package com.github.codeql

import com.github.codeql.comments.CommentExtractorLighterAST
import com.github.codeql.comments.CommentExtractorPSI
import com.github.codeql.utils.*
import com.github.codeql.utils.versions.*
import com.semmle.extractor.java.OdasaOutput
import java.io.Closeable
import java.util.*
import kotlin.collections.ArrayList
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.builtins.functions.BuiltInFunctionArity
import org.jetbrains.kotlin.config.JvmAnalysisFlags
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.classFqName
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.types.isNullableAny
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.types.typeWithArguments
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrStarProjection
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeArgument
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.java.NOT_NULL_ANNOTATIONS
import org.jetbrains.kotlin.load.java.NULLABLE_ANNOTATIONS
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.JavaConstructor
import org.jetbrains.kotlin.load.java.structure.JavaMethod
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull

open class KotlinFileExtractor(
    override val logger: FileLogger,
    override val tw: FileTrapWriter,
    val linesOfCode: LinesOfCode?,
    val filePath: String,
    dependencyCollector: OdasaOutput.TrapFileManager?,
    externalClassExtractor: ExternalDeclExtractor,
    classInstanceStack: ClassInstanceStack,
    primitiveTypeMapping: PrimitiveTypeMapping,
    pluginContext: IrPluginContext,
    val declarationStack: DeclarationStack,
    globalExtensionState: KotlinExtractorGlobalState,
) :
    KotlinUsesExtractor(
        logger,
        tw,
        dependencyCollector,
        externalClassExtractor,
        classInstanceStack,
        primitiveTypeMapping,
        pluginContext,
        globalExtensionState
    ) {

    val usesK2 = usesK2(pluginContext)
    val metaAnnotationSupport = MetaAnnotationSupport(logger, pluginContext, this)

    private inline fun <T> with(kind: String, element: IrElement, f: () -> T): T {
        val name =
            when (element) {
                is IrFile -> element.name
                is IrDeclarationWithName -> element.name.asString()
                else -> "<no name>"
            }
        val loc = tw.getLocationString(element)
        val context = logger.loggerBase.extractorContextStack
        context.push(ExtractorContext(kind, element, name, loc))
        try {
            val depth = context.size
            val depthDescription = "${"-".repeat(depth)} (${depth.toString()})"
            logger.trace("$depthDescription: Starting a $kind ($name) at $loc")
            val result = f()
            logger.trace("$depthDescription: Finished a $kind ($name) at $loc")
            return result
        } catch (exception: Exception) {
            throw Exception("While extracting a $kind ($name) at $loc", exception)
        } finally {
            context.pop()
        }
    }

    fun extractFileContents(file: IrFile, id: Label<DbFile>) {
        with("file", file) {
            val locId = tw.getWholeFileLocation()
            val pkg = file.packageFqName.asString()
            val pkgId = extractPackage(pkg)
            tw.writeHasLocation(id, locId)
            tw.writeCupackage(id, pkgId)

            val exceptionOnFile =
                System.getenv("CODEQL_KOTLIN_INTERNAL_EXCEPTION_WHILE_EXTRACTING_FILE")
            if (exceptionOnFile != null) {
                if (exceptionOnFile.lowercase() == file.name.lowercase()) {
                    throw Exception("Internal testing exception")
                }
            }

            file.declarations.forEach {
                extractDeclaration(
                    it,
                    extractPrivateMembers = true,
                    extractFunctionBodies = true,
                    extractAnnotations = true
                )
                if (it is IrProperty || it is IrField || it is IrFunction) {
                    externalClassExtractor.writeStubTrapFile(it, getTrapFileSignature(it))
                }
            }
            extractStaticInitializer(file, { extractFileClass(file) })
            val psiCommentsExtracted = CommentExtractorPSI(this, file, tw.fileId).extract()
            val lighterAstCommentsExtracted =
                CommentExtractorLighterAST(this, file, tw.fileId).extract()
            if (psiCommentsExtracted == lighterAstCommentsExtracted) {
                if (psiCommentsExtracted) {
                    logger.warnElement(
                        "Found both PSI and LighterAST comments in ${file.path}.",
                        file
                    )
                } else {
                    logger.warnElement("Comments could not be processed in ${file.path}.", file)
                }
            }

            if (!declarationStack.isEmpty()) {
                logger.errorElement(
                    "Declaration stack is not empty after processing the file",
                    file
                )
            }

            linesOfCode?.linesOfCodeInFile(id)

            externalClassExtractor.writeStubTrapFile(file)
        }
    }

    private fun javaBinaryDeclaresMethod(c: IrClass, name: String) =
        ((c.source as? JavaSourceElement)?.javaElement as? BinaryJavaClass)?.methods?.any {
            it.name.asString() == name
        }

    private fun isJavaBinaryDeclaration(f: IrFunction) =
        f.parentClassOrNull?.let { javaBinaryDeclaresMethod(it, f.name.asString()) } ?: false

    private fun isJavaBinaryObjectMethodRedeclaration(d: IrDeclaration) =
        when (d) {
            is IrFunction ->
                when (d.name.asString()) {
                    "toString" -> d.valueParameters.isEmpty()
                    "hashCode" -> d.valueParameters.isEmpty()
                    "equals" -> d.valueParameters.singleOrNull()?.type?.isNullableAny() ?: false
                    else -> false
                } && isJavaBinaryDeclaration(d)
            else -> false
        }

    private fun FunctionDescriptor.tryIsHiddenToOvercomeSignatureClash(d: IrFunction): Boolean {
        // `org.jetbrains.kotlin.ir.descriptors.IrBasedClassConstructorDescriptor.isHiddenToOvercomeSignatureClash`
        // throws one exception or other in Kotlin 2, depending on the version.
        // TODO: We need a replacement for this for Kotlin 2
        try {
            return this.isHiddenToOvercomeSignatureClash
        } catch (e: NotImplementedError) {
            if (!usesK2) {
                logger.warnElement("Couldn't query if element is fake, deciding it's not.", d, e)
            }
            return false
        } catch (e: IllegalStateException) {
            if (!usesK2) {
                logger.warnElement("Couldn't query if element is fake, deciding it's not.", d, e)
            }
            return false
        }
    }

    @OptIn(ObsoleteDescriptorBasedAPI::class)
    fun isFake(d: IrDeclarationWithVisibility): Boolean {
        val hasFakeVisibility =
            d.visibility.let {
                it is DelegatedDescriptorVisibility && it.delegate == Visibilities.InvisibleFake
            } || d.isFakeOverride
        if (hasFakeVisibility && !isJavaBinaryObjectMethodRedeclaration(d)) return true
        return (d as? IrFunction)?.descriptor?.tryIsHiddenToOvercomeSignatureClash(d) == true
    }

    private fun shouldExtractDecl(declaration: IrDeclaration, extractPrivateMembers: Boolean) =
        extractPrivateMembers || !isPrivate(declaration)

    fun extractDeclaration(
        declaration: IrDeclaration,
        extractPrivateMembers: Boolean,
        extractFunctionBodies: Boolean,
        extractAnnotations: Boolean
    ) {
        with("declaration", declaration) {
            if (!shouldExtractDecl(declaration, extractPrivateMembers)) return
            when (declaration) {
                is IrClass -> {
                    if (isExternalDeclaration(declaration)) {
                        extractExternalClassLater(declaration)
                    } else {
                        extractClassSource(
                            declaration,
                            extractDeclarations = true,
                            extractStaticInitializer = true,
                            extractPrivateMembers = extractPrivateMembers,
                            extractFunctionBodies = extractFunctionBodies
                        )
                    }
                }
                is IrFunction -> {
                    val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
                    if (parentId != null) {
                        extractFunction(
                            declaration,
                            parentId,
                            extractBody = extractFunctionBodies,
                            extractMethodAndParameterTypeAccesses = extractFunctionBodies,
                            extractAnnotations = extractAnnotations,
                            null,
                            listOf()
                        )
                    }
                    Unit
                }
                is IrAnonymousInitializer -> {
                    // Leaving this intentionally empty. init blocks are extracted during class
                    // extraction.
                }
                is IrProperty -> {
                    val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
                    if (parentId != null) {
                        extractProperty(
                            declaration,
                            parentId,
                            extractBackingField = true,
                            extractFunctionBodies = extractFunctionBodies,
                            extractPrivateMembers = extractPrivateMembers,
                            extractAnnotations = extractAnnotations,
                            null,
                            listOf()
                        )
                    }
                    Unit
                }
                is IrEnumEntry -> {
                    val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
                    if (parentId != null) {
                        extractEnumEntry(
                            declaration,
                            parentId,
                            extractPrivateMembers,
                            extractFunctionBodies
                        )
                    }
                    Unit
                }
                is IrField -> {
                    val parentId = useDeclarationParentOf(declaration, false)?.cast<DbReftype>()
                    if (parentId != null) {
                        // For consistency with the Java extractor, enum entries get type accesses
                        // only if we're extracting from .kt source (i.e., when
                        // `extractFunctionBodies` is set)
                        extractField(
                            declaration,
                            parentId,
                            extractAnnotationEnumTypeAccesses = extractFunctionBodies
                        )
                    }
                    Unit
                }
                is IrTypeAlias -> extractTypeAlias(declaration)
                else ->
                    logger.errorElement(
                        "Unrecognised IrDeclaration: " + declaration.javaClass,
                        declaration
                    )
            }
        }
    }

    private fun extractTypeParameter(
        tp: IrTypeParameter,
        apparentIndex: Int,
        javaTypeParameter: JavaTypeParameter?
    ): Label<out DbTypevariable>? {
        with("type parameter", tp) {
            val parentId = getTypeParameterParentLabel(tp) ?: return null
            val id = tw.getLabelFor<DbTypevariable>(getTypeParameterLabel(tp))

            // Note apparentIndex does not necessarily equal `tp.index`, because at least
            // constructor type parameters
            // have indices offset from the type parameters of the constructed class (i.e. the
            // parameter S of
            // `class Generic<T> { public <S> Generic(T t, S s) { ... } }` will have `tp.index` 1,
            // not 0).
            tw.writeTypeVars(id, tp.name.asString(), apparentIndex, parentId)
            val locId = tw.getLocation(tp)
            tw.writeHasLocation(id, locId)

            // Annoyingly, we have no obvious way to pair up the bounds of an IrTypeParameter and a
            // JavaTypeParameter
            // because JavaTypeParameter provides a Collection not an ordered list, so we can only
            // do our best here:
            fun tryGetJavaBound(idx: Int) =
                when (tp.superTypes.size) {
                    1 -> javaTypeParameter?.upperBounds?.singleOrNull()
                    else -> (javaTypeParameter?.upperBounds as? List)?.getOrNull(idx)
                }

            tp.superTypes.forEachIndexed { boundIdx, bound ->
                if (!(bound.isAny() || bound.isNullableAny())) {
                    tw.getLabelFor<DbTypebound>("@\"bound;$boundIdx;{$id}\"") {
                        // Note we don't look for @JvmSuppressWildcards here because it doesn't seem
                        // to have any impact
                        // on kotlinc adding wildcards to type parameter bounds.
                        val boundWithWildcards =
                            addJavaLoweringWildcards(bound, true, tryGetJavaBound(tp.index))
                        tw.writeTypeBounds(
                            it,
                            useType(boundWithWildcards).javaResult.id.cast<DbReftype>(),
                            boundIdx,
                            id
                        )
                    }
                }
            }

            if (tp.isReified) {
                addModifiers(id, "reified")
            }

            if (tp.variance == Variance.IN_VARIANCE) {
                addModifiers(id, "in")
            } else if (tp.variance == Variance.OUT_VARIANCE) {
                addModifiers(id, "out")
            }

            // extractAnnotations(tp, id)
            // TODO: introduce annotations once they can be disambiguated from bounds, which are
            // also child expressions.
            return id
        }
    }

    private fun extractVisibility(
        elementForLocation: IrElement,
        id: Label<out DbModifiable>,
        v: DescriptorVisibility
    ) {
        with("visibility", elementForLocation) {
            when (v) {
                DescriptorVisibilities.PRIVATE -> addModifiers(id, "private")
                DescriptorVisibilities.PRIVATE_TO_THIS -> addModifiers(id, "private")
                DescriptorVisibilities.PROTECTED -> addModifiers(id, "protected")
                DescriptorVisibilities.PUBLIC -> addModifiers(id, "public")
                DescriptorVisibilities.INTERNAL -> addModifiers(id, "internal")
                DescriptorVisibilities.LOCAL ->
                    if (elementForLocation is IrFunction && elementForLocation.isLocalFunction()) {
                        // The containing class is `private`.
                        addModifiers(id, "public")
                    } else {
                        addVisibilityModifierToLocalOrAnonymousClass(id)
                    }
                is DelegatedDescriptorVisibility -> {
                    when (v.delegate) {
                        JavaVisibilities.ProtectedStaticVisibility -> {
                            addModifiers(id, "protected")
                            addModifiers(id, "static")
                        }
                        JavaVisibilities.PackageVisibility -> {
                            // default java visibility (top level)
                        }
                        JavaVisibilities.ProtectedAndPackage -> {
                            addModifiers(id, "protected")
                        }
                        else ->
                            logger.errorElement(
                                "Unexpected delegated visibility: $v",
                                elementForLocation
                            )
                    }
                }
                else -> logger.errorElement("Unexpected visibility: $v", elementForLocation)
            }
        }
    }

    private fun extractClassModifiers(c: IrClass, id: Label<out DbClassorinterface>) {
        with("class modifiers", c) {
            when (c.modality) {
                Modality.FINAL -> addModifiers(id, "final")
                Modality.SEALED -> addModifiers(id, "sealed")
                Modality.OPEN -> {} // This is the default
                Modality.ABSTRACT -> addModifiers(id, "abstract")
                else -> logger.errorElement("Unexpected class modality: ${c.modality}", c)
            }
            extractVisibility(c, id, c.visibility)
        }
    }

    fun extractClassInstance(
        classLabel: Label<out DbClassorinterface>,
        c: IrClass,
        argsIncludingOuterClasses: List<IrTypeArgument>?,
        shouldExtractOutline: Boolean,
        shouldExtractDetails: Boolean
    ) {
        DeclarationStackAdjuster(c).use {
            if (shouldExtractOutline) {
                extractClassWithoutMembers(c, argsIncludingOuterClasses)
            }

            if (shouldExtractDetails) {
                val supertypeMode =
                    if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw
                    else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
                extractClassSupertypes(c, classLabel, supertypeMode, true)
                extractNonPrivateMemberPrototypes(c, argsIncludingOuterClasses, classLabel)
            }
        }
    }

    // `argsIncludingOuterClasses` can be null to describe a raw generic type.
    // For non-generic types it will be zero-length list.
    private fun extractClassWithoutMembers(
        c: IrClass,
        argsIncludingOuterClasses: List<IrTypeArgument>?
    ): Label<out DbClassorinterface> {
        with("class instance", c) {
            if (argsIncludingOuterClasses?.isEmpty() == true) {
                logger.error("Instance without type arguments: " + c.name.asString())
            }

            val classLabelResults = getClassLabel(c, argsIncludingOuterClasses)
            val id = tw.getLabelFor<DbClassorinterface>(classLabelResults.classLabel)
            val pkg = c.packageFqName?.asString() ?: ""
            val cls = classLabelResults.shortName
            val pkgId = extractPackage(pkg)
            // TODO: There's lots of duplication between this and extractClassSource.
            // Can we share it?
            val sourceId = useClassSource(c)
            tw.writeClasses_or_interfaces(id, cls, pkgId, sourceId)
            if (c.isInterfaceLike) {
                tw.writeIsInterface(id)
            } else {
                val kind = c.kind
                if (kind == ClassKind.ENUM_CLASS) {
                    tw.writeIsEnumType(id)
                } else if (
                    kind != ClassKind.CLASS &&
                        kind != ClassKind.OBJECT &&
                        kind != ClassKind.ENUM_ENTRY
                ) {
                    logger.errorElement("Unrecognised class kind $kind", c)
                }
            }

            val typeArgs = removeOuterClassTypeArgs(c, argsIncludingOuterClasses)
            if (typeArgs != null) {
                // From 1.9, the list might change when we call erase,
                // so we make a copy that it is safe to iterate over.
                val typeArgsCopy = typeArgs.toList()
                for ((idx, arg) in typeArgsCopy.withIndex()) {
                    val argId = getTypeArgumentLabel(arg).id
                    tw.writeTypeArgs(argId, idx, id)
                }
                tw.writeIsParameterized(id)
            } else {
                tw.writeIsRaw(id)
            }

            extractClassModifiers(c, id)
            classInstanceStack.push(c)
            try {
                extractClassSupertypes(
                    c,
                    id,
                    if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw
                    else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
                )
            } finally {
                classInstanceStack.pop()
            }

            val locId = getLocation(c, argsIncludingOuterClasses)
            tw.writeHasLocation(id, locId)

            // Extract the outer <-> inner class relationship, passing on any type arguments in
            // excess to this class' parameters if this is an inner class.
            // For example, in `class Outer<T> { inner class Inner<S> { } }`, `Inner<Int, String>`
            // nests within `Outer<Int>` and raw `Inner<>` within `Outer<>`,
            // but for a similar non-`inner` (in Java terms, static nested) class both `Inner<Int>`
            // and `Inner<>` nest within the unbound type `Outer`.
            val useBoundOuterType =
                (c.isInner || c.isLocal) &&
                    (c.parents.firstNotNullOfOrNull {
                        when (it) {
                            is IrClass ->
                                when {
                                    it.typeParameters.isNotEmpty() ->
                                        true // Type parameters visible to this class -- extract an
                                             // enclosing bound or raw type.
                                    !(it.isInner || it.isLocal) ->
                                        false // No type parameters seen yet, and this is a static
                                              // class -- extract an enclosing unbound type.
                                    else ->
                                        null // No type parameters seen here, but may be visible
                                             // enclosing type parameters; keep searching.
                                }
                            else ->
                                null // Look through enclosing non-class entities (this may need to
                                     // change)
                        }
                    } ?: false)

            extractEnclosingClass(
                c.parent,
                id,
                c,
                locId,
                if (useBoundOuterType) argsIncludingOuterClasses?.drop(c.typeParameters.size)
                else listOf()
            )

            return id
        }
    }

    private fun getLocation(
        decl: IrDeclaration,
        typeArgs: List<IrTypeArgument>?
    ): Label<DbLocation> {
        return if (typeArgs != null && typeArgs.isNotEmpty()) {
            val binaryPath = getIrDeclarationBinaryPath(decl)
            if (binaryPath == null) {
                tw.getLocation(decl)
            } else {
                val newTrapWriter = tw.makeFileTrapWriter(binaryPath, true)
                newTrapWriter.getWholeFileLocation()
            }
        } else {
            tw.getLocation(decl)
        }
    }

    private fun makeTypeParamSubstitution(
        c: IrClass,
        argsIncludingOuterClasses: List<IrTypeArgument>?
    ) =
        when (argsIncludingOuterClasses) {
            null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() }
            else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses)
        }

    fun extractDeclarationPrototype(
        d: IrDeclaration,
        parentId: Label<out DbReftype>,
        argsIncludingOuterClasses: List<IrTypeArgument>?,
        typeParamSubstitutionQ: TypeSubstitution? = null
    ) {
        val typeParamSubstitution =
            typeParamSubstitutionQ
                ?: when (val parent = d.parent) {
                    is IrClass -> makeTypeParamSubstitution(parent, argsIncludingOuterClasses)
                    else -> {
                        logger.warnElement("Unable to extract prototype of local declaration", d)
                        return
                    }
                }
        when (d) {
            is IrFunction ->
                extractFunction(
                    d,
                    parentId,
                    extractBody = false,
                    extractMethodAndParameterTypeAccesses = false,
                    extractAnnotations = false,
                    typeParamSubstitution,
                    argsIncludingOuterClasses
                )
            is IrProperty ->
                extractProperty(
                    d,
                    parentId,
                    extractBackingField = false,
                    extractFunctionBodies = false,
                    extractPrivateMembers = false,
                    extractAnnotations = false,
                    typeParamSubstitution,
                    argsIncludingOuterClasses
                )
            else -> {}
        }
    }

    // `argsIncludingOuterClasses` can be null to describe a raw generic type.
    // For non-generic types it will be zero-length list.
    private fun extractNonPrivateMemberPrototypes(
        c: IrClass,
        argsIncludingOuterClasses: List<IrTypeArgument>?,
        id: Label<out DbClassorinterface>
    ) {
        with("member prototypes", c) {
            val typeParamSubstitution = makeTypeParamSubstitution(c, argsIncludingOuterClasses)

            c.declarations.map {
                if (shouldExtractDecl(it, false)) {
                    extractDeclarationPrototype(
                        it,
                        id,
                        argsIncludingOuterClasses,
                        typeParamSubstitution
                    )
                }
            }
        }
    }

    private fun extractLocalTypeDeclStmt(
        c: IrClass,
        callable: Label<out DbCallable>,
        parent: Label<out DbStmtparent>,
        idx: Int
    ) {
        val id =
            extractClassSource(
                c,
                extractDeclarations = true,
                extractStaticInitializer = true,
                extractPrivateMembers = true,
                extractFunctionBodies = true
            )
        extractLocalTypeDeclStmt(id, c, callable, parent, idx)
    }

    private fun extractLocalTypeDeclStmt(
        id: Label<out DbClassorinterface>,
        locElement: IrElement,
        callable: Label<out DbCallable>,
        parent: Label<out DbStmtparent>,
        idx: Int
    ) {
        val stmtId = tw.getFreshIdLabel<DbLocaltypedeclstmt>()
        tw.writeStmts_localtypedeclstmt(stmtId, parent, idx, callable)
        tw.writeIsLocalClassOrInterface(id, stmtId)
        val locId = tw.getLocation(locElement)
        tw.writeHasLocation(stmtId, locId)
    }

    private fun extractObinitFunction(c: IrClass, parentId: Label<out DbClassorinterface>) {
        // add method:
        val obinitLabel = getObinitLabel(c, parentId)
        val obinitId = tw.getLabelFor<DbMethod>(obinitLabel)
        val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
        tw.writeMethods(
            obinitId,
            "<obinit>",
            "<obinit>()",
            returnType.javaResult.id,
            parentId,
            obinitId
        )
        tw.writeMethodsKotlinType(obinitId, returnType.kotlinResult.id)

        val locId = tw.getLocation(c)
        tw.writeHasLocation(obinitId, locId)

        addModifiers(obinitId, "private")

        // add body:
        val blockId = extractBlockBody(obinitId, locId)

        extractDeclInitializers(c.declarations, false) { Pair(blockId, obinitId) }
    }

    private val javaLangDeprecated by lazy { referenceExternalClass("java.lang.Deprecated") }

    private val javaLangDeprecatedConstructor by lazy {
        javaLangDeprecated?.constructors?.singleOrNull()
    }

    private fun replaceKotlinDeprecatedAnnotation(
        annotations: List<IrConstructorCall>
    ): List<IrConstructorCall> {
        val shouldReplace =
            annotations.any {
                (it.type as? IrSimpleType)?.classFqName?.asString() == "kotlin.Deprecated"
            } && annotations.none { it.type.classOrNull == javaLangDeprecated?.symbol }
        val jldConstructor = javaLangDeprecatedConstructor
        if (!shouldReplace || jldConstructor == null) return annotations
        return annotations.filter {
            (it.type as? IrSimpleType)?.classFqName?.asString() != "kotlin.Deprecated"
        } +
            // Note we lose any arguments to @java.lang.Deprecated that were written in source.
            IrConstructorCallImpl.fromSymbolOwner(
                UNDEFINED_OFFSET,
                UNDEFINED_OFFSET,
                jldConstructor.returnType,
                jldConstructor.symbol,
                0
            )
    }

    private fun extractAnnotations(
        c: IrAnnotationContainer,
        annotations: List<IrConstructorCall>,
        parent: Label<out DbExprparent>,
        extractEnumTypeAccesses: Boolean
    ) {
        val origin =
            (c as? IrDeclaration)?.origin
                ?: run {
                    logger.warn("Unexpected annotation container: $c")
                    return
                }
        val replacedAnnotations =
            if (origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB)
                replaceKotlinDeprecatedAnnotation(annotations)
            else annotations
        val groupedAnnotations =
            metaAnnotationSupport.groupRepeatableAnnotations(replacedAnnotations)
        for ((idx, constructorCall: IrConstructorCall) in
            groupedAnnotations.sortedBy { v -> v.type.classFqName?.asString() }.withIndex()) {
            extractAnnotation(constructorCall, parent, idx, extractEnumTypeAccesses)
        }
    }

    private fun extractAnnotations(
        c: IrAnnotationContainer,
        parent: Label<out DbExprparent>,
        extractEnumTypeAccesses: Boolean
    ) {
        extractAnnotations(c, c.annotations, parent, extractEnumTypeAccesses)
    }

    private fun extractAnnotation(
        constructorCall: IrConstructorCall,
        parent: Label<out DbExprparent>,
        idx: Int,
        extractEnumTypeAccesses: Boolean,
        contextLabel: String? = null
    ): Label<out DbExpr> {
        // Erase the type here because the JVM lowering erases the annotation type, and so the Java
        // extractor will see it in erased form.
        val t = useType(erase(constructorCall.type))
        val annotationContextLabel = contextLabel ?: "{${t.javaResult.id}}"
        val id =
            tw.getLabelFor<DbDeclannotation>("@\"annotation;{$parent};$annotationContextLabel\"")
        tw.writeExprs_declannotation(id, t.javaResult.id, parent, idx)
        tw.writeExprsKotlinType(id, t.kotlinResult.id)

        val locId = tw.getLocation(constructorCall)
        tw.writeHasLocation(id, locId)

        for (i in 0 until constructorCall.valueArgumentsCount) {
            val param = constructorCall.symbol.owner.valueParameters[i]
            val prop =
                constructorCall.symbol.owner.parentAsClass.declarations
                    .filterIsInstance<IrProperty>()
                    .first { it.name == param.name }
            val v = constructorCall.getValueArgument(i) ?: param.defaultValue?.expression
            val getter = prop.getter
            if (getter == null) {
                logger.warnElement("Expected annotation property to define a getter", prop)
            } else {
                val getterId = useFunction<DbMethod>(getter)
                if (getterId == null) {
                    logger.errorElement("Couldn't get ID for getter", getter)
                } else {
                    val exprId =
                        extractAnnotationValueExpression(
                            v,
                            id,
                            i,
                            "{$getterId}",
                            getter.returnType,
                            extractEnumTypeAccesses
                        )
                    if (exprId != null) {
                        tw.writeAnnotValue(id, getterId, exprId)
                    }
                }
            }
        }
        return id
    }

    private fun extractAnnotationValueExpression(
        v: IrExpression?,
        parent: Label<out DbExprparent>,
        idx: Int,
        contextLabel: String,
        contextType: IrType?,
        extractEnumTypeAccesses: Boolean
    ): Label<out DbExpr>? {

        fun exprId() = tw.getLabelFor<DbExpr>("@\"annotationExpr;{$parent};$idx\"")

        return when (v) {
            is CodeQLIrConst<*> -> {
                extractConstant(v, parent, idx, null, null, overrideId = exprId())
            }
            is IrGetEnumValue -> {
                extractEnumValue(
                    v,
                    parent,
                    idx,
                    null,
                    null,
                    extractTypeAccess = extractEnumTypeAccesses,
                    overrideId = exprId()
                )
            }
            is IrClassReference -> {
                val classRefId = exprId()
                val typeAccessId =
                    tw.getLabelFor<DbUnannotatedtypeaccess>("@\"annotationExpr;{$classRefId};0\"")
                extractClassReference(
                    v,
                    parent,
                    idx,
                    null,
                    null,
                    overrideId = classRefId,
                    typeAccessOverrideId = typeAccessId,
                    useJavaLangClassType = true
                )
            }
            is IrConstructorCall -> {
                extractAnnotation(v, parent, idx, extractEnumTypeAccesses, contextLabel)
            }
            is IrVararg -> {
                tw.getLabelFor<DbArrayinit>("@\"annotationarray;{$parent};$contextLabel\"").also {
                    arrayId ->
                    // Use the context type (i.e., the type the annotation expects, not the actual
                    // type of the array)
                    // because the Java extractor fills in array types using the same technique.
                    // These should only
                    // differ for generic annotations.
                    if (contextType == null) {
                        logger.warnElement(
                            "Expected an annotation array to have an enclosing context",
                            v
                        )
                    } else {
                        val type = useType(kClassToJavaClass(contextType))
                        tw.writeExprs_arrayinit(arrayId, type.javaResult.id, parent, idx)
                        tw.writeExprsKotlinType(arrayId, type.kotlinResult.id)
                        tw.writeHasLocation(arrayId, tw.getLocation(v))

                        v.elements.forEachIndexed { index, irVarargElement ->
                            run {
                                val argExpr =
                                    when (irVarargElement) {
                                        is IrExpression -> irVarargElement
                                        is IrSpreadElement -> irVarargElement.expression
                                        else -> {
                                            logger.errorElement(
                                                "Unrecognised IrVarargElement: " +
                                                    irVarargElement.javaClass,
                                                irVarargElement
                                            )
                                            null
                                        }
                                    }
                                extractAnnotationValueExpression(
                                    argExpr,
                                    arrayId,
                                    index,
                                    "child;$index",
                                    null,
                                    extractEnumTypeAccesses
                                )
                            }
                        }
                    }
                }
            }
            // is IrErrorExpression
            // null
            // Note: emitting an ErrorExpr here would induce an inconsistency if this annotation is
            // later seen from source or by the Java extractor,
            // in both of which cases the real value will get extracted.
            else -> null
        }
    }

    fun extractClassSource(
        c: IrClass,
        extractDeclarations: Boolean,
        extractStaticInitializer: Boolean,
        extractPrivateMembers: Boolean,
        extractFunctionBodies: Boolean
    ): Label<out DbClassorinterface> {
        with("class source", c) {
            DeclarationStackAdjuster(c).use {
                val id = useClassSource(c)
                val pkg = c.packageFqName?.asString() ?: ""
                val cls = if (c.isAnonymousObject) "" else c.name.asString()
                val pkgId = extractPackage(pkg)
                tw.writeClasses_or_interfaces(id, cls, pkgId, id)
                if (c.isInterfaceLike) {
                    tw.writeIsInterface(id)
                    if (c.kind == ClassKind.ANNOTATION_CLASS) {
                        tw.writeIsAnnotType(id)
                    }
                } else {
                    val kind = c.kind
                    if (kind == ClassKind.ENUM_CLASS) {
                        tw.writeIsEnumType(id)
                    } else if (
                        kind != ClassKind.CLASS &&
                            kind != ClassKind.OBJECT &&
                            kind != ClassKind.ENUM_ENTRY
                    ) {
                        logger.warnElement("Unrecognised class kind $kind", c)
                    }

                    if (c.origin == IrDeclarationOrigin.FILE_CLASS) {
                        tw.writeFile_class(id)
                    }

                    if (c.isData) {
                        tw.writeKtDataClasses(id)
                    }
                }

                val locId = tw.getLocation(c)
                tw.writeHasLocation(id, locId)

                extractEnclosingClass(c.parent, id, c, locId, listOf())

                val javaClass = (c.source as? JavaSourceElement)?.javaElement as? JavaClass

                c.typeParameters.mapIndexed { idx, param ->
                    extractTypeParameter(param, idx, javaClass?.typeParameters?.getOrNull(idx))
                }
                if (extractDeclarations) {
                    if (c.kind == ClassKind.ANNOTATION_CLASS) {
                        c.declarations.filterIsInstance<IrProperty>().forEach {
                            val getter = it.getter
                            if (getter == null) {
                                logger.warnElement(
                                    "Expected an annotation property to have a getter",
                                    it
                                )
                            } else {
                                extractFunction(
                                        getter,
                                        id,
                                        extractBody = false,
                                        extractMethodAndParameterTypeAccesses =
                                            extractFunctionBodies,
                                        extractAnnotations = true,
                                        null,
                                        listOf()
                                    )
                                    ?.also { functionLabel ->
                                        tw.writeIsAnnotElem(functionLabel.cast())
                                    }
                            }
                        }
                    } else {
                        try {
                            c.declarations.forEach {
                                extractDeclaration(
                                    it,
                                    extractPrivateMembers = extractPrivateMembers,
                                    extractFunctionBodies = extractFunctionBodies,
                                    extractAnnotations = true
                                )
                            }
                            if (extractStaticInitializer) extractStaticInitializer(c, { id })
                            extractJvmStaticProxyMethods(
                                c,
                                id,
                                extractPrivateMembers,
                                extractFunctionBodies
                            )
                        } catch (e: IllegalArgumentException) {
                            // A Kotlin bug causes this to throw: https://youtrack.jetbrains.com/issue/KT-63847/K2-IllegalStateException-IrFieldPublicSymbolImpl-for-java.time-Clock.OffsetClock.offset0-is-already-bound
                            // TODO: This should either be removed or log something, once the bug is fixed
                        }
                    }
                }
                if (c.isNonCompanionObject) {
                    // For `object MyObject { ... }`, the .class has an
                    // automatically-generated `public static final MyObject INSTANCE`
                    // field that may be referenced from Java code, and is used in our
                    // IrGetObjectValue support. We therefore need to fabricate it
                    // here.
                    val instance = useObjectClassInstance(c)
                    val type = useSimpleTypeClass(c, emptyList(), false)
                    tw.writeFields(instance.id, instance.name, type.javaResult.id, id)
                    tw.writeFieldsKotlinType(instance.id, type.kotlinResult.id)
                    tw.writeHasLocation(instance.id, locId)
                    addModifiers(instance.id, "public", "static", "final")
                    tw.writeClass_object(id, instance.id)
                }
                if (c.isObject) {
                    addModifiers(id, "static")
                }
                if (extractFunctionBodies && needsObinitFunction(c)) {
                    extractObinitFunction(c, id)
                }

                extractClassModifiers(c, id)
                extractClassSupertypes(
                    c,
                    id,
                    inReceiverContext = true
                ) // inReceiverContext = true is specified to force extraction of member prototypes
                  // of base types

                linesOfCode?.linesOfCodeInDeclaration(c, id)

                val additionalAnnotations =
                    if (
                        c.kind == ClassKind.ANNOTATION_CLASS &&
                            c.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
                    )
                        metaAnnotationSupport.generateJavaMetaAnnotations(c, extractFunctionBodies)
                    else listOf()

                extractAnnotations(
                    c,
                    c.annotations + additionalAnnotations,
                    id,
                    extractFunctionBodies
                )

                if (extractFunctionBodies && !c.isAnonymousObject && !c.isLocal)
                    externalClassExtractor.writeStubTrapFile(c)

                return id
            }
        }
    }

    val jvmStaticFqName = FqName("kotlin.jvm.JvmStatic")

    private fun extractJvmStaticProxyMethods(
        c: IrClass,
        classId: Label<out DbClassorinterface>,
        extractPrivateMembers: Boolean,
        extractFunctionBodies: Boolean
    ) {

        // Add synthetic forwarders for any JvmStatic methods or properties:
        val companionObject = c.companionObject() ?: return

        val cType = c.typeWith()
        val companionType = companionObject.typeWith()

        fun makeProxyFunction(f: IrFunction) {
            // Emit a function in class `c` that delegates to the same function defined on
            // `c.CompanionInstance`.
            val proxyFunctionId = tw.getLabelFor<DbMethod>(getFunctionLabel(f, classId, listOf()))
            // We extract the function prototype with its ID overridden to belong to `c` not the
            // companion object,
            // but suppress outputting the body, which we will replace with a delegating call below.
            forceExtractFunction(
                f,
                classId,
                extractBody = false,
                extractMethodAndParameterTypeAccesses = extractFunctionBodies,
                extractAnnotations = false,
                typeSubstitution = null,
                classTypeArgsIncludingOuterClasses = listOf(),
                extractOrigin = false,
                OverriddenFunctionAttributes(id = proxyFunctionId)
            )
            addModifiers(proxyFunctionId, "static")
            tw.writeCompiler_generated(
                proxyFunctionId,
                CompilerGeneratedKinds.JVMSTATIC_PROXY_METHOD.kind
            )
            if (extractFunctionBodies) {
                val realFunctionLocId = tw.getLocation(f)
                extractExpressionBody(proxyFunctionId, realFunctionLocId).also { returnId ->
                    extractRawMethodAccess(
                        f,
                        realFunctionLocId,
                        f.returnType,
                        proxyFunctionId,
                        returnId,
                        0,
                        returnId,
                        f.valueParameters.size,
                        { argParent, idxOffset ->
                            f.valueParameters.forEachIndexed { idx, param ->
                                val syntheticParamId = useValueParameter(param, proxyFunctionId)
                                extractVariableAccess(
                                    syntheticParamId,
                                    param.type,
                                    realFunctionLocId,
                                    argParent,
                                    idxOffset + idx,
                                    proxyFunctionId,
                                    returnId
                                )
                            }
                        },
                        companionType,
                        { callId ->
                            val companionField =
                                useCompanionObjectClassInstance(companionObject)?.id
                            extractVariableAccess(
                                    companionField,
                                    companionType,
                                    realFunctionLocId,
                                    callId,
                                    -1,
                                    proxyFunctionId,
                                    returnId
                                )
                                .also { varAccessId ->
                                    extractTypeAccessRecursive(
                                        cType,
                                        realFunctionLocId,
                                        varAccessId,
                                        -1,
                                        proxyFunctionId,
                                        returnId
                                    )
                                }
                        },
                        null
                    )
                }
            }
        }

        companionObject.declarations.forEach {
            if (shouldExtractDecl(it, extractPrivateMembers)) {
                val wholeDeclAnnotated = it.hasAnnotation(jvmStaticFqName)
                when (it) {
                    is IrFunction -> {
                        if (wholeDeclAnnotated) {
                            makeProxyFunction(it)
                            if (it.hasAnnotation(jvmOverloadsFqName)) {
                                extractGeneratedOverloads(
                                    it,
                                    classId,
                                    classId,
                                    extractFunctionBodies,
                                    extractMethodAndParameterTypeAccesses = extractFunctionBodies,
                                    typeSubstitution = null,
                                    classTypeArgsIncludingOuterClasses = listOf()
                                )
                            }
                        }
                    }
                    is IrProperty -> {
                        it.getter?.let { getter ->
                            if (wholeDeclAnnotated || getter.hasAnnotation(jvmStaticFqName))
                                makeProxyFunction(getter)
                        }
                        it.setter?.let { setter ->
                            if (wholeDeclAnnotated || setter.hasAnnotation(jvmStaticFqName))
                                makeProxyFunction(setter)
                        }
                    }
                }
            }
        }
    }

    /**
     * This function traverses the declaration-parent hierarchy upwards, and retrieves the enclosing
     * class of a class to extract the `enclInReftype` relation. Additionally, it extracts a
     * companion field for a companion object into its parent class.
     *
     * Note that the nested class can also be a local class declared inside a function, so the
     * upwards traversal is skipping the non-class parents. Also, in some cases the file class is
     * the enclosing one, which has no IR representation.
     */
    private fun extractEnclosingClass(
        declarationParent:
            IrDeclarationParent, // The declaration parent of the element for which we are
                                 // extracting the enclosing class
        innerId: Label<out DbClassorinterface>, // ID of the inner class
        innerClass:
            IrClass?, // The inner class, if available. It's not available if the enclosing class of
                      // a generated class is being extracted
        innerLocId: Label<DbLocation>, // Location of the inner class
        parentClassTypeArguments:
            List<
                IrTypeArgument
            >? // Type arguments of the parent class. If `parentClassTypeArguments` is null, the
               // parent class is a raw type
    ) {
        with("enclosing class", declarationParent) {
            var parent: IrDeclarationParent? = declarationParent
            while (parent != null) {
                if (parent is IrClass) {
                    val parentId = useClassInstance(parent, parentClassTypeArguments).typeResult.id
                    tw.writeEnclInReftype(innerId, parentId)
                    if (innerClass != null && innerClass.isCompanion) {
                        // If we are a companion then our parent has a
                        //     public static final ParentClass$CompanionObjectClass
                        // CompanionObjectName;
                        // that we need to fabricate here
                        val instance = useCompanionObjectClassInstance(innerClass)
                        if (instance != null) {
                            val type = useSimpleTypeClass(innerClass, emptyList(), false)
                            tw.writeFields(
                                instance.id,
                                instance.name,
                                type.javaResult.id,
                                parentId
                            )
                            tw.writeFieldsKotlinType(instance.id, type.kotlinResult.id)
                            tw.writeHasLocation(instance.id, innerLocId)
                            addModifiers(instance.id, "public", "static", "final")
                            tw.writeType_companion_object(parentId, instance.id, innerId)
                        }
                    }

                    break
                } else if (parent is IrFile) {
                    if (innerClass != null && !innerClass.isLocal) {
                        // We don't have to extract file class containers for classes except for
                        // local classes
                        break
                    }
                    if (this.filePath != parent.path) {
                        logger.error("Unexpected file parent found")
                    }
                    val fileId = extractFileClass(parent)
                    tw.writeEnclInReftype(innerId, fileId)
                    break
                }

                parent = (parent as? IrDeclaration)?.parent
            }
        }
    }

    private data class FieldResult(val id: Label<DbField>, val name: String)

    private fun useCompanionObjectClassInstance(c: IrClass): FieldResult? {
        val parent = c.parent
        if (!c.isCompanion) {
            logger.error("Using companion instance for non-companion class")
            return null
        } else if (parent !is IrClass) {
            logger.error("Using companion instance for non-companion class")
            return null
        } else {
            val parentId = useClassInstance(parent, listOf()).typeResult.id
            val instanceName = c.name.asString()
            val instanceLabel = "@\"field;{$parentId};$instanceName\""
            val instanceId: Label<DbField> = tw.getLabelFor(instanceLabel)
            return FieldResult(instanceId, instanceName)
        }
    }

    private fun useObjectClassInstance(c: IrClass): FieldResult {
        if (!c.isNonCompanionObject) {
            logger.error("Using instance for non-object class")
        }
        val classId = useClassInstance(c, listOf()).typeResult.id
        val instanceName = "INSTANCE"
        val instanceLabel = "@\"field;{$classId};$instanceName\""
        val instanceId: Label<DbField> = tw.getLabelFor(instanceLabel)
        return FieldResult(instanceId, instanceName)
    }

    @OptIn(ObsoleteDescriptorBasedAPI::class)
    private fun hasSynthesizedParameterNames(f: IrFunction) =
        f.descriptor.hasSynthesizedParameterNames()

    private fun extractValueParameter(
        vp: IrValueParameter,
        parent: Label<out DbCallable>,
        idx: Int,
        typeSubstitution: TypeSubstitution?,
        parentSourceDeclaration: Label<out DbCallable>,
        classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?,
        extractTypeAccess: Boolean,
        locOverride: Label<DbLocation>? = null
    ): TypeResults {
        with("value parameter", vp) {
            val location = locOverride ?: getLocation(vp, classTypeArgsIncludingOuterClasses)
            val maybeAlteredType =
                (vp.parent as? IrFunction)?.let {
                    if (overridesCollectionsMethodWithAlteredParameterTypes(it))
                        eraseCollectionsMethodParameterType(vp.type, it.name.asString(), idx)
                    else if (
                        (vp.parent as? IrConstructor)?.parentClassOrNull?.kind ==
                            ClassKind.ANNOTATION_CLASS
                    )
                        kClassToJavaClass(vp.type)
                    else null
                } ?: vp.type
            val javaType =
                (vp.parent as? IrFunction)?.let {
                    getJavaCallable(it)?.let { jCallable ->
                        getJavaValueParameterType(jCallable, idx)
                    }
                }
            val typeWithWildcards =
                addJavaLoweringWildcards(
                    maybeAlteredType,
                    !getInnermostWildcardSupppressionAnnotation(vp),
                    javaType
                )
            val substitutedType =
                typeSubstitution?.let { it(typeWithWildcards, TypeContext.OTHER, pluginContext) }
                    ?: typeWithWildcards
            val id = useValueParameter(vp, parent)
            if (extractTypeAccess) {
                extractTypeAccessRecursive(substitutedType, location, id, -1)
            }
            val syntheticParameterNames =
                isUnderscoreParameter(vp) ||
                    ((vp.parent as? IrFunction)?.let { hasSynthesizedParameterNames(it) } ?: true)
            val javaParameter =
                when (val callable = (vp.parent as? IrFunction)?.let { getJavaCallable(it) }) {
                    is JavaConstructor -> callable.valueParameters.getOrNull(idx)
                    is JavaMethod -> callable.valueParameters.getOrNull(idx)
                    else -> null
                }
            val extraAnnotations =
                listOfNotNull(
                    getNullabilityAnnotation(
                        vp.type,
                        vp.origin,
                        vp.annotations,
                        javaParameter?.annotations
                    )
                )
            extractAnnotations(vp, vp.annotations + extraAnnotations, id, extractTypeAccess)
            return extractValueParameter(
                id,
                substitutedType,
                vp.name.asString(),
                location,
                parent,
                idx,
                useValueParameter(vp, parentSourceDeclaration),
                syntheticParameterNames,
                vp.isVararg,
                vp.isNoinline,
                vp.isCrossinline
            )
        }
    }

    private fun extractValueParameter(
        id: Label<out DbParam>,
        t: IrType,
        name: String,
        locId: Label<DbLocation>,
        parent: Label<out DbCallable>,
        idx: Int,
        paramSourceDeclaration: Label<out DbParam>,
        syntheticParameterNames: Boolean,
        isVararg: Boolean,
        isNoinline: Boolean,
        isCrossinline: Boolean
    ): TypeResults {
        val type = useType(t)
        tw.writeParams(id, type.javaResult.id, idx, parent, paramSourceDeclaration)
        tw.writeParamsKotlinType(id, type.kotlinResult.id)
        tw.writeHasLocation(id, locId)
        if (!syntheticParameterNames) {
            tw.writeParamName(id, name)
        }
        if (isVararg) {
            tw.writeIsVarargsParam(id)
        }
        if (isNoinline) {
            addModifiers(id, "noinline")
        }
        if (isCrossinline) {
            addModifiers(id, "crossinline")
        }
        return type
    }

    /**
     * mkContainerLabel is a lambda so that we get laziness: If the container is a file, then we
     * don't want to extract the file class unless something actually needs it.
     */
    private fun extractStaticInitializer(
        container: IrDeclarationContainer,
        mkContainerLabel: () -> Label<out DbClassorinterface>
    ) {
        with("static initializer extraction", container) {
            extractDeclInitializers(container.declarations, true) {
                val containerId = mkContainerLabel()
                val clinitLabel =
                    getFunctionLabel(
                        container,
                        containerId,
                        "<clinit>",
                        listOf(),
                        pluginContext.irBuiltIns.unitType,
                        extensionParamType = null,
                        functionTypeParameters = listOf(),
                        classTypeArgsIncludingOuterClasses = listOf(),
                        overridesCollectionsMethod = false,
                        javaSignature = null,
                        addParameterWildcardsByDefault = false
                    )
                val clinitId = tw.getLabelFor<DbMethod>(clinitLabel)
                val returnType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
                tw.writeMethods(
                    clinitId,
                    "<clinit>",
                    "<clinit>()",
                    returnType.javaResult.id,
                    containerId,
                    clinitId
                )
                tw.writeMethodsKotlinType(clinitId, returnType.kotlinResult.id)

                tw.writeCompiler_generated(
                    clinitId,
                    CompilerGeneratedKinds.CLASS_INITIALISATION_METHOD.kind
                )

                val locId = tw.getWholeFileLocation()
                tw.writeHasLocation(clinitId, locId)

                addModifiers(clinitId, "static")

                // add and return body block:
                Pair(extractBlockBody(clinitId, locId), clinitId)
            }
        }
    }

    private fun extractInstanceInitializerBlock(
        parent: StmtParent,
        enclosingConstructor: IrConstructor
    ) {
        with("object initializer block", enclosingConstructor) {
            val constructorId = useFunction<DbConstructor>(enclosingConstructor)
            if (constructorId == null) {
                logger.errorElement("Cannot get ID for constructor", enclosingConstructor)
                return
            }
            val enclosingClass = enclosingConstructor.parentClassOrNull
            if (enclosingClass == null) {
                logger.errorElement("Constructor's parent is not a class", enclosingConstructor)
                return
            }

            // Don't make this block lazily since we need to insert something at the given
            // parent.idx position,
            // and in the case where there are no initializers to emit an empty block is an
            // acceptable filler.
            val initBlockId =
                tw.getFreshIdLabel<DbBlock>().also {
                    tw.writeStmts_block(it, parent.parent, parent.idx, constructorId)
                    val locId = tw.getLocation(enclosingConstructor)
                    tw.writeHasLocation(it, locId)
                }
            extractDeclInitializers(enclosingClass.declarations, false) {
                Pair(initBlockId, constructorId)
            }
        }
    }

    private fun extractDeclInitializers(
        declarations: List<IrDeclaration>,
        extractStaticInitializers: Boolean,
        makeEnclosingBlock: () -> Pair<Label<DbBlock>, Label<out DbCallable>>
    ) {
        val blockAndFunctionId by lazy { makeEnclosingBlock() }

        // Extract field initializers and init blocks (the latter can only occur in object
        // initializers)
        var idx = 0

        fun extractFieldInitializer(f: IrDeclaration) {
            val static: Boolean
            val initializer: IrExpressionBody?
            val lhsType: TypeResults?
            val vId: Label<out DbVariable>?
            val isAnnotationClassField: Boolean
            if (f is IrField) {
                static = f.isStatic
                initializer = f.initializer
                isAnnotationClassField = isAnnotationClassField(f)
                lhsType = useType(if (isAnnotationClassField) kClassToJavaClass(f.type) else f.type)
                vId = useField(f)
            } else if (f is IrEnumEntry) {
                static = true
                initializer = f.initializerExpression
                isAnnotationClassField = false
                lhsType = getEnumEntryType(f)
                if (lhsType == null) {
                    return
                }
                vId = useEnumEntry(f)
            } else {
                return
            }

            if (static != extractStaticInitializers || initializer == null) {
                return
            }

            val expr = initializer.expression

            val declLocId = tw.getLocation(f)
            extractExpressionStmt(
                    declLocId,
                    blockAndFunctionId.first,
                    idx++,
                    blockAndFunctionId.second
                )
                .also { stmtId ->
                    val type =
                        if (isAnnotationClassField) kClassToJavaClass(expr.type) else expr.type
                    extractAssignExpr(type, declLocId, stmtId, 0, blockAndFunctionId.second, stmtId)
                        .also { assignmentId ->
                            tw.writeKtInitializerAssignment(assignmentId)
                            extractVariableAccess(
                                    vId,
                                    lhsType,
                                    declLocId,
                                    assignmentId,
                                    0,
                                    blockAndFunctionId.second,
                                    stmtId
                                )
                                .also { lhsId ->
                                    if (static) {
                                        extractStaticTypeAccessQualifier(
                                            f,
                                            lhsId,
                                            declLocId,
                                            blockAndFunctionId.second,
                                            stmtId
                                        )
                                    }
                                }
                            extractExpressionExpr(
                                expr,
                                blockAndFunctionId.second,
                                assignmentId,
                                1,
                                stmtId
                            )
                        }
                }
        }

        for (decl in declarations) {
            when (decl) {
                is IrProperty -> {
                    decl.backingField?.let { extractFieldInitializer(it) }
                }
                is IrField -> {
                    extractFieldInitializer(decl)
                }
                is IrEnumEntry -> {
                    extractFieldInitializer(decl)
                }
                is IrAnonymousInitializer -> {
                    if (decl.isStatic != extractStaticInitializers) {
                        continue
                    }

                    for (stmt in decl.body.statements) {
                        extractStatement(
                            stmt,
                            blockAndFunctionId.second,
                            blockAndFunctionId.first,
                            idx++
                        )
                    }
                }
                else -> continue
            }
        }
    }

    private fun isKotlinDefinedInterface(cls: IrClass?) =
        cls != null &&
            cls.isInterface &&
            cls.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB

    private fun needsInterfaceForwarder(f: IrFunction) =
        // jvmDefaultModeIsNoCompatibility means that -Xjvm-default=all or =no-compatibility was
        // used, in which case real Java default interfaces are used, and we don't need to do
        // anything.
        // Otherwise, for a Kotlin-defined method inheriting a Kotlin-defined default, we need to
        // create a synthetic method like
        // `int f(int x) { return super.InterfaceWithDefault.f(x); }`, because kotlinc will generate
        // a public method and Java callers may directly target it.
        // (NB. kotlinc's actual implementation strategy is different -- it makes an inner class
        // called InterfaceWithDefault$DefaultImpls and stores the default methods
        // there to allow default method usage in Java < 8, but this is hopefully niche.
        !jvmDefaultModeIsNoCompatibility(getJvmDefaultMode(pluginContext.languageVersionSettings)) &&
            f.parentClassOrNull.let {
                it != null &&
                    it.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB &&
                    it.modality != Modality.ABSTRACT
            } &&
            f.realOverrideTarget.let {
                it != f &&
                    (it as? IrSimpleFunction)?.modality != Modality.ABSTRACT &&
                    isKotlinDefinedInterface(it.parentClassOrNull)
            }

    private fun makeInterfaceForwarder(
        f: IrFunction,
        parentId: Label<out DbReftype>,
        extractBody: Boolean,
        extractMethodAndParameterTypeAccesses: Boolean,
        typeSubstitution: TypeSubstitution?,
        classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
    ) =
        forceExtractFunction(
                f,
                parentId,
                extractBody = false,
                extractMethodAndParameterTypeAccesses,
                extractAnnotations = false,
                typeSubstitution,
                classTypeArgsIncludingOuterClasses,
                overriddenAttributes =
                    OverriddenFunctionAttributes(
                        visibility = DescriptorVisibilities.PUBLIC,
                        modality = Modality.OPEN
                    )
            )
            .also { functionId ->
                tw.writeCompiler_generated(
                    functionId,
                    CompilerGeneratedKinds.INTERFACE_FORWARDER.kind
                )
                if (extractBody) {
                    val realFunctionLocId = tw.getLocation(f)
                    val inheritedDefaultFunction = f.realOverrideTarget
                    val directlyInheritedSymbol =
                        when (f) {
                            is IrSimpleFunction ->
                                f.overriddenSymbols.find { it.owner === inheritedDefaultFunction }
                                    ?: f.overriddenSymbols.find {
                                        it.owner.realOverrideTarget === inheritedDefaultFunction
                                    }
                                    ?: inheritedDefaultFunction.symbol
                            else ->
                                inheritedDefaultFunction
                                    .symbol // This is strictly invalid, since we shouldn't use
                                            // A.super.f(...) where A may not be a direct supertype,
                                            // but this path should also be unreachable.
                        }
                    val defaultDefiningInterfaceType =
                        (directlyInheritedSymbol.owner.parentClassOrNull ?: return functionId)
                            .typeWith()

                    extractExpressionBody(functionId, realFunctionLocId).also { returnId ->
                        extractRawMethodAccess(
                            f,
                            realFunctionLocId,
                            f.returnType,
                            functionId,
                            returnId,
                            0,
                            returnId,
                            f.valueParameters.size,
                            { argParentId, idxOffset ->
                                f.valueParameters.mapIndexed { idx, param ->
                                    val syntheticParamId = useValueParameter(param, functionId)
                                    extractVariableAccess(
                                        syntheticParamId,
                                        param.type,
                                        realFunctionLocId,
                                        argParentId,
                                        idxOffset + idx,
                                        functionId,
                                        returnId
                                    )
                                }
                            },
                            f.dispatchReceiverParameter?.type,
                            { callId ->
                                extractSuperAccess(
                                    defaultDefiningInterfaceType,
                                    functionId,
                                    callId,
                                    -1,
                                    returnId,
                                    realFunctionLocId
                                )
                            },
                            null
                        )
                    }
                }
            }

    private fun extractFunction(
        f: IrFunction,
        parentId: Label<out DbReftype>,
        extractBody: Boolean,
        extractMethodAndParameterTypeAccesses: Boolean,
        extractAnnotations: Boolean,
        typeSubstitution: TypeSubstitution?,
        classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
    ) =
        if (isFake(f)) {
            if (needsInterfaceForwarder(f))
                makeInterfaceForwarder(
                    f,
                    parentId,
                    extractBody,
                    extractMethodAndParameterTypeAccesses,
                    typeSubstitution,
                    classTypeArgsIncludingOuterClasses
                )
            else null
        } else {
            // Work around an apparent bug causing redeclarations of `fun toString(): String`
            // specifically in interfaces loaded from Java classes show up like fake overrides.
            val overriddenVisibility =
                if (f.isFakeOverride && isJavaBinaryObjectMethodRedeclaration(f))
                    OverriddenFunctionAttributes(visibility = DescriptorVisibilities.PUBLIC)
                else null
            forceExtractFunction(
                    f,
                    parentId,
                    extractBody,
                    extractMethodAndParameterTypeAccesses,
                    extractAnnotations,
                    typeSubstitution,
                    classTypeArgsIncludingOuterClasses,
                    overriddenAttributes = overriddenVisibility
                )
                .also {
                    // The defaults-forwarder function is a static utility, not a member, so we only
                    // need to extract this for the unspecialised instance of this class.
                    if (classTypeArgsIncludingOuterClasses.isNullOrEmpty())
                        extractDefaultsFunction(
                            f,
                            parentId,
                            extractBody,
                            extractMethodAndParameterTypeAccesses
                        )
                    extractGeneratedOverloads(
                        f,
                        parentId,
                        null,
                        extractBody,
                        extractMethodAndParameterTypeAccesses,
                        typeSubstitution,
                        classTypeArgsIncludingOuterClasses
                    )
                }
        }

    private fun extractDefaultsFunction(
        f: IrFunction,
        parentId: Label<out DbReftype>,
        extractBody: Boolean,
        extractMethodAndParameterTypeAccesses: Boolean
    ) {
        if (f.valueParameters.none { it.defaultValue != null }) return

        val id = getDefaultsMethodLabel(f)
        if (id == null) {
            logger.errorElement("Cannot get defaults method label for function", f)
            return
        }
        val locId = getLocation(f, null)
        val extReceiver = f.extensionReceiverParameter
        val dispatchReceiver = if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter
        val parameterTypes = getDefaultsMethodArgTypes(f)
        val allParamTypeResults =
            parameterTypes.mapIndexed { i, paramType ->
                val paramId = tw.getLabelFor<DbParam>(getValueParameterLabel(id, i))
                extractValueParameter(
                        paramId,
                        paramType,
                        "p$i",
                        locId,
                        id,
                        i,
                        paramId,
                        isVararg = false,
                        syntheticParameterNames = true,
                        isCrossinline = false,
                        isNoinline = false
                    )
                    .also {
                        if (extractMethodAndParameterTypeAccesses)
                            extractTypeAccess(useType(paramType), locId, paramId, -1)
                    }
            }
        val paramsSignature =
            allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") {
                signatureOrWarn(it.javaResult, f)
            }
        val shortName = getDefaultsMethodName(f)

        if (f.symbol is IrConstructorSymbol) {
            val constrId = id.cast<DbConstructor>()
            extractConstructor(constrId, shortName, paramsSignature, parentId, constrId)
        } else {
            val methodId = id.cast<DbMethod>()
            extractMethod(
                methodId,
                locId,
                shortName,
                erase(f.returnType),
                paramsSignature,
                parentId,
                methodId,
                origin = null,
                extractTypeAccess = extractMethodAndParameterTypeAccesses
            )
            addModifiers(id, "static")
            if (extReceiver != null) {
                val idx = if (dispatchReceiver != null) 1 else 0
                val extendedType = allParamTypeResults[idx]
                tw.writeKtExtensionFunctions(
                    methodId,
                    extendedType.javaResult.id,
                    extendedType.kotlinResult.id
                )
            }
        }
        tw.writeHasLocation(id, locId)
        if (
            f.visibility != DescriptorVisibilities.PRIVATE &&
                f.visibility != DescriptorVisibilities.PRIVATE_TO_THIS
        ) {
            // Private methods have package-private (default) visibility $default methods; all other
            // visibilities seem to produce a public $default method.
            addModifiers(id, "public")
        }
        tw.writeCompiler_generated(id, CompilerGeneratedKinds.DEFAULT_ARGUMENTS_METHOD.kind)

        if (extractBody) {
            val nonSyntheticParams = listOfNotNull(dispatchReceiver) + f.valueParameters
            // This stack entry represents as if we're extracting the 'real' function `f`, giving
            // the indices of its non-synthetic parameters
            // such that when we extract the default expressions below, any reference to f's nth
            // parameter will resolve to f$default's
            // n + o'th parameter, where `o` is the parameter offset caused by adding any dispatch
            // receiver to the parameter list.
            // Note we don't need to add the extension receiver here because `useValueParameter`
            // always assumes an extension receiver
            // will be prepended if one exists.
            val realFunctionId = useFunction<DbCallable>(f, parentId, null)
            DeclarationStackAdjuster(
                    f,
                    OverriddenFunctionAttributes(
                        id,
                        id,
                        locId,
                        nonSyntheticParams,
                        typeParameters = listOf(),
                        isStatic = true
                    )
                )
                .use {
                    val realParamsVarId = getValueParameterLabel(id, parameterTypes.size - 2)
                    val intType = pluginContext.irBuiltIns.intType
                    val paramIdxOffset =
                        listOf(dispatchReceiver, f.extensionReceiverParameter).count { it != null }
                    extractBlockBody(id, locId).also { blockId ->
                        var nextStmt = 0
                        // For each parameter with a default, sub in the default value if the caller
                        // hasn't supplied a value:
                        f.valueParameters.forEachIndexed { paramIdx, param ->
                            val defaultVal = param.defaultValue
                            if (defaultVal != null) {
                                extractIfStmt(locId, blockId, nextStmt++, id).also { ifId ->
                                    // if (realParams & thisParamBit == 0) ...
                                    extractEqualsExpression(locId, ifId, 0, id, ifId).also { eqId ->
                                        extractAndbitExpression(intType, locId, eqId, 0, id, ifId)
                                            .also { opId ->
                                                extractConstantInteger(
                                                    1 shl paramIdx,
                                                    locId,
                                                    opId,
                                                    0,
                                                    id,
                                                    ifId
                                                )
                                                extractVariableAccess(
                                                    tw.getLabelFor<DbParam>(realParamsVarId),
                                                    intType,
                                                    locId,
                                                    opId,
                                                    1,
                                                    id,
                                                    ifId
                                                )
                                            }
                                        extractConstantInteger(0, locId, eqId, 1, id, ifId)
                                    }
                                    // thisParamVar = defaultExpr...
                                    extractExpressionStmt(locId, ifId, 1, id).also { exprStmtId ->
                                        extractAssignExpr(
                                                param.type,
                                                locId,
                                                exprStmtId,
                                                0,
                                                id,
                                                exprStmtId
                                            )
                                            .also { assignId ->
                                                extractVariableAccess(
                                                    tw.getLabelFor<DbParam>(
                                                        getValueParameterLabel(
                                                            id,
                                                            paramIdx + paramIdxOffset
                                                        )
                                                    ),
                                                    param.type,
                                                    locId,
                                                    assignId,
                                                    0,
                                                    id,
                                                    exprStmtId
                                                )
                                                extractExpressionExpr(
                                                    defaultVal.expression,
                                                    id,
                                                    assignId,
                                                    1,
                                                    exprStmtId
                                                )
                                            }
                                    }
                                }
                            }
                        }
                        // Now call the real function:
                        if (f is IrConstructor) {
                            tw.getFreshIdLabel<DbConstructorinvocationstmt>().also { thisCallId ->
                                tw.writeStmts_constructorinvocationstmt(
                                    thisCallId,
                                    blockId,
                                    nextStmt++,
                                    id
                                )
                                tw.writeHasLocation(thisCallId, locId)
                                f.valueParameters.forEachIndexed { idx, param ->
                                    extractVariableAccess(
                                        tw.getLabelFor<DbParam>(getValueParameterLabel(id, idx)),
                                        param.type,
                                        locId,
                                        thisCallId,
                                        idx,
                                        id,
                                        thisCallId
                                    )
                                }
                                tw.writeCallableBinding(thisCallId, realFunctionId)
                            }
                        } else {
                            tw.getFreshIdLabel<DbReturnstmt>().also { returnId ->
                                tw.writeStmts_returnstmt(returnId, blockId, nextStmt++, id)
                                tw.writeHasLocation(returnId, locId)
                                extractMethodAccessWithoutArgs(
                                        f.returnType,
                                        locId,
                                        id,
                                        returnId,
                                        0,
                                        returnId,
                                        realFunctionId
                                    )
                                    .also { thisCallId ->
                                        val realFnIdxOffset =
                                            if (f.extensionReceiverParameter != null) 1 else 0
                                        val paramMappings =
                                            f.valueParameters.mapIndexed { idx, param ->
                                                Triple(
                                                    param.type,
                                                    idx + paramIdxOffset,
                                                    idx + realFnIdxOffset
                                                )
                                            } +
                                                listOfNotNull(
                                                    dispatchReceiver?.let {
                                                        Triple(it.type, 0, -1)
                                                    },
                                                    extReceiver?.let {
                                                        Triple(
                                                            it.type,
                                                            if (dispatchReceiver != null) 1 else 0,
                                                            0
                                                        )
                                                    }
                                                )
                                        paramMappings.forEach { (type, fromIdx, toIdx) ->
                                            extractVariableAccess(
                                                tw.getLabelFor<DbParam>(
                                                    getValueParameterLabel(id, fromIdx)
                                                ),
                                                type,
                                                locId,
                                                thisCallId,
                                                toIdx,
                                                id,
                                                returnId
                                            )
                                        }
                                        if (f.shouldExtractAsStatic)
                                            extractStaticTypeAccessQualifier(
                                                f,
                                                thisCallId,
                                                locId,
                                                id,
                                                returnId
                                            )
                                        else if (f.isLocalFunction()) {
                                            extractNewExprForLocalFunction(
                                                getLocallyVisibleFunctionLabels(f),
                                                thisCallId,
                                                locId,
                                                id,
                                                returnId
                                            )
                                        }
                                    }
                            }
                        }
                    }
                }
        }
    }

    private val jvmOverloadsFqName = FqName("kotlin.jvm.JvmOverloads")

    private fun extractGeneratedOverloads(
        f: IrFunction,
        parentId: Label<out DbReftype>,
        maybeSourceParentId: Label<out DbReftype>?,
        extractBody: Boolean,
        extractMethodAndParameterTypeAccesses: Boolean,
        typeSubstitution: TypeSubstitution?,
        classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
    ) {

        fun extractGeneratedOverload(paramList: List<IrValueParameter?>) {
            val overloadParameters = paramList.filterNotNull()
            // Note `overloadParameters` have incorrect parents and indices, since there is no
            // actual IrFunction describing the required synthetic overload.
            // We have to use the `overriddenAttributes` element of `DeclarationStackAdjuster` to
            // fix up references to these parameters while we're extracting
            // these synthetic overloads.
            val overloadId =
                tw.getLabelFor<DbCallable>(
                    getFunctionLabel(
                        f,
                        parentId,
                        classTypeArgsIncludingOuterClasses,
                        overloadParameters
                    )
                )
            val sourceParentId =
                maybeSourceParentId
                    ?: if (typeSubstitution != null) useDeclarationParentOf(f, false) else parentId
            if (sourceParentId == null) {
                logger.errorElement("Cannot get source parent ID for function", f)
                return
            }
            val sourceDeclId =
                tw.getLabelFor<DbCallable>(
                    getFunctionLabel(f, sourceParentId, listOf(), overloadParameters)
                )
            val overriddenAttributes =
                OverriddenFunctionAttributes(
                    id = overloadId,
                    sourceDeclarationId = sourceDeclId,
                    valueParameters = overloadParameters
                )
            forceExtractFunction(
                f,
                parentId,
                extractBody = false,
                extractMethodAndParameterTypeAccesses,
                extractAnnotations = false,
                typeSubstitution,
                classTypeArgsIncludingOuterClasses,
                overriddenAttributes = overriddenAttributes
            )
            tw.writeCompiler_generated(overloadId, CompilerGeneratedKinds.JVMOVERLOADS_METHOD.kind)
            val realFunctionLocId = tw.getLocation(f)
            if (extractBody) {

                DeclarationStackAdjuster(f, overriddenAttributes).use {

                    // Create a synthetic function body that calls the corresponding $default
                    // function:
                    val regularArgs =
                        paramList.map { it?.let { p -> IrGetValueImpl(-1, -1, p.symbol) } }

                    if (f is IrConstructor) {
                        val blockId = extractBlockBody(overloadId, realFunctionLocId)
                        val constructorCallId = tw.getFreshIdLabel<DbConstructorinvocationstmt>()
                        tw.writeStmts_constructorinvocationstmt(
                            constructorCallId,
                            blockId,
                            0,
                            overloadId
                        )
                        tw.writeHasLocation(constructorCallId, realFunctionLocId)
                        tw.writeCallableBinding(
                            constructorCallId,
                            getDefaultsMethodLabel(f, parentId)
                        )

                        extractDefaultsCallArguments(
                            constructorCallId,
                            f,
                            overloadId,
                            constructorCallId,
                            regularArgs,
                            null,
                            null
                        )
                    } else {
                        val dispatchReceiver =
                            f.dispatchReceiverParameter?.let { IrGetValueImpl(-1, -1, it.symbol) }
                        val extensionReceiver =
                            f.extensionReceiverParameter?.let { IrGetValueImpl(-1, -1, it.symbol) }

                        extractExpressionBody(overloadId, realFunctionLocId).also { returnId ->
                            extractsDefaultsCall(
                                f,
                                realFunctionLocId,
                                f.returnType,
                                overloadId,
                                returnId,
                                0,
                                returnId,
                                regularArgs,
                                dispatchReceiver,
                                extensionReceiver
                            )
                        }
                    }
                }
            }
        }

        if (!f.hasAnnotation(jvmOverloadsFqName)) {
            if (
                f is IrConstructor &&
                    f.valueParameters.isNotEmpty() &&
                    f.valueParameters.all { it.defaultValue != null } &&
                    f.parentClassOrNull?.let {
                        // Don't create a default constructor for an annotation class, or a class
                        // that explicitly declares a no-arg constructor.
                        !it.isAnnotationClass &&
                            it.declarations.none { d ->
                                d is IrConstructor && d.valueParameters.isEmpty()
                            }
                    } == true
            ) {
                // Per https://kotlinlang.org/docs/classes.html#creating-instances-of-classes, a
                // single default overload gets created specifically
                // when we have all default parameters, regardless of `@JvmOverloads`.
                extractGeneratedOverload(f.valueParameters.map { _ -> null })
            }
            return
        }

        val paramList: MutableList<IrValueParameter?> = f.valueParameters.toMutableList()
        for (n in (f.valueParameters.size - 1) downTo 0) {
            if (f.valueParameters[n].defaultValue != null) {
                paramList[n] = null // Remove this parameter, to be replaced by a default value
                extractGeneratedOverload(paramList)
            }
        }
    }

    private fun extractConstructor(
        id: Label<out DbConstructor>,
        shortName: String,
        paramsSignature: String,
        parentId: Label<out DbReftype>,
        sourceDeclaration: Label<out DbConstructor>
    ) {
        val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
        tw.writeConstrs(
            id,
            shortName,
            "$shortName$paramsSignature",
            unitType.javaResult.id,
            parentId,
            sourceDeclaration
        )
        tw.writeConstrsKotlinType(id, unitType.kotlinResult.id)
    }

    private fun extractMethod(
        id: Label<out DbMethod>,
        locId: Label<out DbLocation>,
        shortName: String,
        returnType: IrType,
        paramsSignature: String,
        parentId: Label<out DbReftype>,
        sourceDeclaration: Label<out DbMethod>,
        origin: IrDeclarationOrigin?,
        extractTypeAccess: Boolean
    ) {
        val returnTypeResults = useType(returnType, TypeContext.RETURN)
        tw.writeMethods(
            id,
            shortName,
            "$shortName$paramsSignature",
            returnTypeResults.javaResult.id,
            parentId,
            sourceDeclaration
        )
        tw.writeMethodsKotlinType(id, returnTypeResults.kotlinResult.id)
        when (origin) {
            IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER ->
                tw.writeCompiler_generated(
                    id,
                    CompilerGeneratedKinds.GENERATED_DATA_CLASS_MEMBER.kind
                )
            IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR ->
                tw.writeCompiler_generated(
                    id,
                    CompilerGeneratedKinds.DEFAULT_PROPERTY_ACCESSOR.kind
                )
            IrDeclarationOrigin.ENUM_CLASS_SPECIAL_MEMBER ->
                tw.writeCompiler_generated(
                    id,
                    CompilerGeneratedKinds.ENUM_CLASS_SPECIAL_MEMBER.kind
                )
        }
        if (extractTypeAccess) {
            extractTypeAccessRecursive(returnType, locId, id, -1)
        }
    }

    private fun signatureOrWarn(t: TypeResult<*>, associatedElement: IrElement?) =
        t.signature
            ?: "<signature unavailable>"
                .also {
                    if (associatedElement != null)
                        logger.warnElement(
                            "Needed a signature for a type that doesn't have one",
                            associatedElement
                        )
                    else logger.warn("Needed a signature for a type that doesn't have one")
                }

    private fun getNullabilityAnnotationName(
        t: IrType,
        declOrigin: IrDeclarationOrigin,
        existingAnnotations: List<IrConstructorCall>,
        javaAnnotations: Collection<JavaAnnotation>?
    ): FqName? {
        if (t !is IrSimpleType) return null

        fun hasExistingAnnotation(name: FqName) =
            existingAnnotations.any { existing -> existing.type.classFqName == name }

        return if (declOrigin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) {
            // Java declaration: restore a NotNull or Nullable annotation if the original Java
            // member had one but the Kotlin compiler removed it.
            javaAnnotations
                ?.mapNotNull { it.classId?.asSingleFqName() }
                ?.singleOrNull {
                    NOT_NULL_ANNOTATIONS.contains(it) || NULLABLE_ANNOTATIONS.contains(it)
                }
                ?.takeUnless { hasExistingAnnotation(it) }
        } else {
            // Kotlin declaration: add a NotNull annotation to a non-nullable non-primitive type,
            // unless one is already present.
            // Usually Kotlin declarations can't have a manual `@NotNull`, but this happens at least
            // when delegating members are
            // synthesised and inherit the annotation from the delegate (which given it has
            // @NotNull, is likely written in Java)
            JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION.takeUnless {
                t.isNullableCodeQL() ||
                    primitiveTypeMapping.getPrimitiveInfo(t) != null ||
                    hasExistingAnnotation(it)
            }
        }
    }

    private fun getNullabilityAnnotation(
        t: IrType,
        declOrigin: IrDeclarationOrigin,
        existingAnnotations: List<IrConstructorCall>,
        javaAnnotations: Collection<JavaAnnotation>?
    ) =
        getNullabilityAnnotationName(t, declOrigin, existingAnnotations, javaAnnotations)?.let {
            getClassByFqName(pluginContext, it)?.let { annotationClass ->
                annotationClass.owner.declarations.firstIsInstanceOrNull<IrConstructor>()?.let {
                    annotationConstructor ->
                    IrConstructorCallImpl.fromSymbolOwner(
                        UNDEFINED_OFFSET,
                        UNDEFINED_OFFSET,
                        annotationConstructor.returnType,
                        annotationConstructor.symbol,
                        0
                    )
                }
            }
        }

    private fun forceExtractFunction(
        f: IrFunction,
        parentId: Label<out DbReftype>,
        extractBody: Boolean,
        extractMethodAndParameterTypeAccesses: Boolean,
        extractAnnotations: Boolean,
        typeSubstitution: TypeSubstitution?,
        classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?,
        extractOrigin: Boolean = true,
        overriddenAttributes: OverriddenFunctionAttributes? = null
    ): Label<out DbCallable> {
        with("function", f) {
            DeclarationStackAdjuster(f, overriddenAttributes).use {
                val javaCallable = getJavaCallable(f)
                getFunctionTypeParameters(f).mapIndexed { idx, tp ->
                    extractTypeParameter(
                        tp,
                        idx,
                        (javaCallable as? JavaTypeParameterListOwner)
                            ?.typeParameters
                            ?.getOrNull(idx)
                    )
                }

                val id =
                    overriddenAttributes?.id
                        ?: // If this is a class that would ordinarily be replaced by a Java
                           // equivalent (e.g. kotlin.Map -> java.util.Map),
                        // don't replace here, really extract the Kotlin version:
                        useFunction<DbCallable>(
                            f,
                            parentId,
                            classTypeArgsIncludingOuterClasses,
                            noReplace = true
                        )

                val sourceDeclaration =
                    overriddenAttributes?.sourceDeclarationId
                        ?: if (typeSubstitution != null && overriddenAttributes?.id == null) {
                            val sourceFunId = useFunction<DbCallable>(f)
                            if (sourceFunId == null) {
                                logger.errorElement("Cannot get source ID for function", f)
                                id // TODO: This is wrong; we ought to just fail in this case
                            } else {
                                sourceFunId
                            }
                        } else {
                            id
                        }

                val extReceiver = f.extensionReceiverParameter
                // The following parameter order is correct, because member $default methods (where
                // the order would be [dispatchParam], [extensionParam], normalParams) are not
                // extracted here
                val fParameters =
                    listOfNotNull(extReceiver) +
                        (overriddenAttributes?.valueParameters ?: f.valueParameters)
                val paramTypes =
                    fParameters.mapIndexed { i, vp ->
                        extractValueParameter(
                            vp,
                            id,
                            i,
                            typeSubstitution,
                            sourceDeclaration,
                            classTypeArgsIncludingOuterClasses,
                            extractTypeAccess = extractMethodAndParameterTypeAccesses,
                            overriddenAttributes?.sourceLoc
                        )
                    }
                if (extReceiver != null) {
                    val extendedType = paramTypes[0]
                    tw.writeKtExtensionFunctions(
                        id.cast<DbMethod>(),
                        extendedType.javaResult.id,
                        extendedType.kotlinResult.id
                    )
                }

                val paramsSignature =
                    paramTypes.joinToString(separator = ",", prefix = "(", postfix = ")") {
                        signatureOrWarn(it.javaResult, f)
                    }

                val adjustedReturnType =
                    addJavaLoweringWildcards(
                        getAdjustedReturnType(f),
                        false,
                        (javaCallable as? JavaMethod)?.returnType
                    )
                val substReturnType =
                    typeSubstitution?.let {
                        it(adjustedReturnType, TypeContext.RETURN, pluginContext)
                    } ?: adjustedReturnType

                val locId =
                    overriddenAttributes?.sourceLoc
                        ?: getLocation(f, classTypeArgsIncludingOuterClasses)

                if (f.symbol is IrConstructorSymbol) {
                    val shortName =
                        when {
                            adjustedReturnType.isAnonymous -> ""
                            typeSubstitution != null ->
                                useType(substReturnType).javaResult.shortName
                            else ->
                                adjustedReturnType.classFqName?.shortName()?.asString()
                                    ?: f.name.asString()
                        }
                    extractConstructor(
                        id.cast(),
                        shortName,
                        paramsSignature,
                        parentId,
                        sourceDeclaration.cast()
                    )
                } else {
                    val shortNames = getFunctionShortName(f)
                    val methodId = id.cast<DbMethod>()
                    extractMethod(
                        methodId,
                        locId,
                        shortNames.nameInDB,
                        substReturnType,
                        paramsSignature,
                        parentId,
                        sourceDeclaration.cast(),
                        if (extractOrigin) f.origin else null,
                        extractMethodAndParameterTypeAccesses
                    )

                    if (shortNames.nameInDB != shortNames.kotlinName) {
                        tw.writeKtFunctionOriginalNames(methodId, shortNames.kotlinName)
                    }

                    if (f.hasInterfaceParent() && f.body != null) {
                        addModifiers(
                            methodId,
                            "default"
                        ) // The actual output class file may or may not have this modifier,
                          // depending on the -Xjvm-default setting.
                    }
                }

                tw.writeHasLocation(id, locId)
                val body = f.body
                if (body != null && extractBody) {
                    if (typeSubstitution != null)
                        logger.errorElement(
                            "Type substitution should only be used to extract a function prototype, not the body",
                            f
                        )
                    extractBody(body, id)
                }

                extractVisibility(f, id, overriddenAttributes?.visibility ?: f.visibility)

                if (f.isInline) {
                    addModifiers(id, "inline")
                }
                if (f.shouldExtractAsStatic) {
                    addModifiers(id, "static")
                }
                if (f is IrSimpleFunction && f.overriddenSymbols.isNotEmpty()) {
                    addModifiers(id, "override")
                }
                if (f.isSuspend) {
                    addModifiers(id, "suspend")
                }
                if (f.symbol !is IrConstructorSymbol) {
                    when (overriddenAttributes?.modality ?: (f as? IrSimpleFunction)?.modality) {
                        Modality.ABSTRACT -> addModifiers(id, "abstract")
                        Modality.FINAL -> addModifiers(id, "final")
                        else -> Unit
                    }
                }

                linesOfCode?.linesOfCodeInDeclaration(f, id)

                if (extractAnnotations) {
                    val extraAnnotations =
                        if (f.symbol is IrConstructorSymbol) listOf()
                        else
                            listOfNotNull(
                                getNullabilityAnnotation(
                                    f.returnType,
                                    f.origin,
                                    f.annotations,
                                    getJavaCallable(f)?.annotations
                                )
                            )
                    extractAnnotations(
                        f,
                        f.annotations + extraAnnotations,
                        id,
                        extractMethodAndParameterTypeAccesses
                    )
                }

                return id
            }
        }
    }

    private fun isStaticFunction(f: IrFunction): Boolean {
        return f.dispatchReceiverParameter == null // Has no dispatch receiver,
        &&
            !f
                .isLocalFunction() // not a local function. Local functions are extracted as
                                   // instance methods with the local class instantiation as the
                                   // qualifier
            &&
            f.symbol !is IrConstructorSymbol // not a constructor
    }

    private fun extractField(
        f: IrField,
        parentId: Label<out DbReftype>,
        extractAnnotationEnumTypeAccesses: Boolean
    ): Label<out DbField> {
        with("field", f) {
            DeclarationStackAdjuster(f).use {
                val fNameSuffix =
                    getExtensionReceiverType(f)?.let {
                        it.classFqName?.asString()?.replace(".", "$$")
                    } ?: ""
                val extractType =
                    if (isAnnotationClassField(f)) kClassToJavaClass(f.type) else f.type
                val id = useField(f)
                extractAnnotations(f, id, extractAnnotationEnumTypeAccesses)
                return extractField(
                    id,
                    "${f.name.asString()}$fNameSuffix",
                    extractType,
                    parentId,
                    tw.getLocation(f),
                    f.visibility,
                    f,
                    isExternalDeclaration(f),
                    f.isFinal,
                    isDirectlyExposedCompanionObjectField(f)
                )
            }
        }
    }

    private fun extractField(
        id: Label<out DbField>,
        name: String,
        type: IrType,
        parentId: Label<out DbReftype>,
        locId: Label<DbLocation>,
        visibility: DescriptorVisibility,
        errorElement: IrElement,
        isExternalDeclaration: Boolean,
        isFinal: Boolean,
        isStatic: Boolean
    ): Label<out DbField> {
        val t = useType(type)
        tw.writeFields(id, name, t.javaResult.id, parentId)
        tw.writeFieldsKotlinType(id, t.kotlinResult.id)
        tw.writeHasLocation(id, locId)

        extractVisibility(errorElement, id, visibility)
        if (isFinal) {
            addModifiers(id, "final")
        }
        if (isStatic) {
            addModifiers(id, "static")
        }

        if (!isExternalDeclaration) {
            val fieldDeclarationId = tw.getFreshIdLabel<DbFielddecl>()
            tw.writeFielddecls(fieldDeclarationId, parentId)
            tw.writeFieldDeclaredIn(id, fieldDeclarationId, 0)
            tw.writeHasLocation(fieldDeclarationId, locId)

            extractTypeAccessRecursive(type, locId, fieldDeclarationId, 0)
        }

        return id
    }

    private fun extractProperty(
        p: IrProperty,
        parentId: Label<out DbReftype>,
        extractBackingField: Boolean,
        extractFunctionBodies: Boolean,
        extractPrivateMembers: Boolean,
        extractAnnotations: Boolean,
        typeSubstitution: TypeSubstitution?,
        classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?
    ) {
        with("property", p) {
            fun needsInterfaceForwarderQ(f: IrFunction?) =
                f?.let { needsInterfaceForwarder(f) } ?: false

            if (
                isFake(p) &&
                    !needsInterfaceForwarderQ(p.getter) &&
                    !needsInterfaceForwarderQ(p.setter)
            )
                return

            DeclarationStackAdjuster(p).use {
                val id = useProperty(p, parentId, classTypeArgsIncludingOuterClasses)
                val locId = getLocation(p, classTypeArgsIncludingOuterClasses)
                tw.writeKtProperties(id, p.name.asString())
                tw.writeHasLocation(id, locId)

                val bf = p.backingField
                val getter = p.getter
                val setter = p.setter

                if (getter == null) {
                    if (!isExternalDeclaration(p)) {
                        logger.warnElement("IrProperty without a getter", p)
                    }
                } else if (shouldExtractDecl(getter, extractPrivateMembers)) {
                    val getterId =
                        extractFunction(
                                getter,
                                parentId,
                                extractBody = extractFunctionBodies,
                                extractMethodAndParameterTypeAccesses = extractFunctionBodies,
                                extractAnnotations = extractAnnotations,
                                typeSubstitution,
                                classTypeArgsIncludingOuterClasses
                            )
                            ?.cast<DbMethod>()
                    if (getterId != null) {
                        tw.writeKtPropertyGetters(id, getterId)
                        if (getter.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) {
                            tw.writeCompiler_generated(
                                getterId,
                                CompilerGeneratedKinds.DELEGATED_PROPERTY_GETTER.kind
                            )
                        }
                    }
                }

                if (setter == null) {
                    if (p.isVar && !isExternalDeclaration(p)) {
                        logger.warnElement("isVar property without a setter", p)
                    }
                } else if (shouldExtractDecl(setter, extractPrivateMembers)) {
                    if (!p.isVar) {
                        logger.warnElement("!isVar property with a setter", p)
                    }
                    val setterId =
                        extractFunction(
                                setter,
                                parentId,
                                extractBody = extractFunctionBodies,
                                extractMethodAndParameterTypeAccesses = extractFunctionBodies,
                                extractAnnotations = extractAnnotations,
                                typeSubstitution,
                                classTypeArgsIncludingOuterClasses
                            )
                            ?.cast<DbMethod>()
                    if (setterId != null) {
                        tw.writeKtPropertySetters(id, setterId)
                        if (setter.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) {
                            tw.writeCompiler_generated(
                                setterId,
                                CompilerGeneratedKinds.DELEGATED_PROPERTY_SETTER.kind
                            )
                        }
                    }
                }

                if (bf != null && extractBackingField) {
                    val fieldParentId = useDeclarationParentOf(bf, false)
                    if (fieldParentId != null) {
                        val fieldId = extractField(bf, fieldParentId.cast(), extractFunctionBodies)
                        tw.writeKtPropertyBackingFields(id, fieldId)
                        if (p.isDelegated) {
                            tw.writeKtPropertyDelegates(id, fieldId)
                        }
                    }
                }

                extractVisibility(p, id, p.visibility)

                // TODO: extract annotations

                if (p.isLateinit) {
                    addModifiers(id, "lateinit")
                }
            }
        }
    }

    private fun getEnumEntryType(ee: IrEnumEntry): TypeResults? {
        val parent = ee.parent
        if (parent !is IrClass) {
            logger.errorElement("Enum entry with unexpected parent: " + parent.javaClass, ee)
            return null
        } else if (parent.typeParameters.isNotEmpty()) {
            logger.errorElement("Enum entry parent class has type parameters: " + parent.name, ee)
            return null
        } else {
            return useSimpleTypeClass(parent, emptyList(), false)
        }
    }

    private fun extractEnumEntry(
        ee: IrEnumEntry,
        parentId: Label<out DbReftype>,
        extractPrivateMembers: Boolean,
        extractFunctionBodies: Boolean
    ) {
        with("enum entry", ee) {
            DeclarationStackAdjuster(ee).use {
                val id = useEnumEntry(ee)
                val type = getEnumEntryType(ee) ?: return
                tw.writeFields(id, ee.name.asString(), type.javaResult.id, parentId)
                tw.writeFieldsKotlinType(id, type.kotlinResult.id)
                val locId = tw.getLocation(ee)
                tw.writeHasLocation(id, locId)
                tw.writeIsEnumConst(id)

                if (extractFunctionBodies) {
                    val fieldDeclarationId = tw.getFreshIdLabel<DbFielddecl>()
                    tw.writeFielddecls(fieldDeclarationId, parentId)
                    tw.writeFieldDeclaredIn(id, fieldDeclarationId, 0)
                    tw.writeHasLocation(fieldDeclarationId, locId)

                    extractTypeAccess(type, locId, fieldDeclarationId, 0)
                }

                ee.correspondingClass?.let {
                    extractDeclaration(
                        it,
                        extractPrivateMembers,
                        extractFunctionBodies,
                        extractAnnotations = true
                    )
                }

                extractAnnotations(ee, id, extractFunctionBodies)
            }
        }
    }

    private fun extractTypeAlias(ta: IrTypeAlias) {
        with("type alias", ta) {
            if (ta.typeParameters.isNotEmpty()) {
                // TODO: Extract this information
                return
            }
            val id = useTypeAlias(ta)
            val locId = tw.getLocation(ta)
            // TODO: We don't really want to generate any Java types here; we only want the KT type:
            val type = useType(ta.expandedType)
            tw.writeKt_type_alias(id, ta.name.asString(), type.kotlinResult.id)
            tw.writeHasLocation(id, locId)

            // TODO: extract annotations
        }
    }

    private fun extractBody(b: IrBody, callable: Label<out DbCallable>) {
        with("body", b) {
            when (b) {
                is IrBlockBody -> extractBlockBody(b, callable)
                is IrSyntheticBody -> extractSyntheticBody(b, callable)
                is IrExpressionBody -> extractExpressionBody(b, callable)
                else -> {
                    logger.errorElement("Unrecognised IrBody: " + b.javaClass, b)
                }
            }
        }
    }

    private fun extractBlockBody(callable: Label<out DbCallable>, locId: Label<DbLocation>) =
        tw.getFreshIdLabel<DbBlock>().also {
            tw.writeStmts_block(it, callable, 0, callable)
            tw.writeHasLocation(it, locId)
        }

    private fun extractBlockBody(b: IrBlockBody, callable: Label<out DbCallable>) {
        with("block body", b) {
            extractBlockBody(callable, tw.getLocation(b)).also {
                for ((sIdx, stmt) in b.statements.withIndex()) {
                    extractStatement(stmt, callable, it, sIdx)
                }
            }
        }
    }

    private fun extractSyntheticBody(b: IrSyntheticBody, callable: Label<out DbCallable>) {
        with("synthetic body", b) {
            val kind = b.kind
            when {
                kind == IrSyntheticBodyKind.ENUM_VALUES -> tw.writeKtSyntheticBody(callable, 1)
                kind == IrSyntheticBodyKind.ENUM_VALUEOF -> tw.writeKtSyntheticBody(callable, 2)
                kind == kind_ENUM_ENTRIES -> tw.writeKtSyntheticBody(callable, 3)
                else -> {
                    logger.errorElement("Unhandled synthetic body kind " + kind, b)
                }
            }
        }
    }

    private fun extractExpressionBody(b: IrExpressionBody, callable: Label<out DbCallable>) {
        with("expression body", b) {
            val locId = tw.getLocation(b)
            extractExpressionBody(callable, locId).also { returnId ->
                extractExpressionExpr(b.expression, callable, returnId, 0, returnId)
            }
        }
    }

    fun extractExpressionBody(
        callable: Label<out DbCallable>,
        locId: Label<DbLocation>
    ): Label<out DbStmt> {
        val blockId = extractBlockBody(callable, locId)
        return tw.getFreshIdLabel<DbReturnstmt>().also { returnId ->
            tw.writeStmts_returnstmt(returnId, blockId, 0, callable)
            tw.writeHasLocation(returnId, locId)
        }
    }

    private fun getVariableLocationProvider(v: IrVariable): IrElement {
        val init = v.initializer
        if (v.startOffset < 0 && init != null) {
            // IR_TEMPORARY_VARIABLEs have no proper location
            return init
        }

        return v
    }

    private fun extractVariable(
        v: IrVariable,
        callable: Label<out DbCallable>,
        parent: Label<out DbStmtparent>,
        idx: Int
    ) {
        with("variable", v) {
            val stmtId = tw.getFreshIdLabel<DbLocalvariabledeclstmt>()
            val locId = tw.getLocation(getVariableLocationProvider(v))
            tw.writeStmts_localvariabledeclstmt(stmtId, parent, idx, callable)
            tw.writeHasLocation(stmtId, locId)
            extractVariableExpr(v, callable, stmtId, 1, stmtId)
        }
    }

    private fun extractVariableExpr(
        v: IrVariable,
        callable: Label<out DbCallable>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingStmt: Label<out DbStmt>,
        extractInitializer: Boolean = true
    ) {
        with("variable expr", v) {
            val varId = useVariable(v)
            val exprId = tw.getFreshIdLabel<DbLocalvariabledeclexpr>()
            val locId = tw.getLocation(getVariableLocationProvider(v))
            val type = useType(v.type)
            tw.writeLocalvars(varId, v.name.asString(), type.javaResult.id, exprId)
            tw.writeLocalvarsKotlinType(varId, type.kotlinResult.id)
            tw.writeHasLocation(varId, locId)
            tw.writeExprs_localvariabledeclexpr(exprId, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(exprId, type.kotlinResult.id)
            extractExprContext(exprId, locId, callable, enclosingStmt)
            val i = v.initializer
            if (i != null && extractInitializer) {
                extractExpressionExpr(i, callable, exprId, 0, enclosingStmt)
            }
            if (!v.isVar) {
                addModifiers(varId, "final")
            }
            if (v.isLateinit) {
                addModifiers(varId, "lateinit")
            }
        }
    }

    private fun extractIfStmt(
        locId: Label<DbLocation>,
        parent: Label<out DbStmtparent>,
        idx: Int,
        callable: Label<out DbCallable>
    ) =
        tw.getFreshIdLabel<DbIfstmt>().also {
            tw.writeStmts_ifstmt(it, parent, idx, callable)
            tw.writeHasLocation(it, locId)
        }

    private fun extractStatement(
        s: IrStatement,
        callable: Label<out DbCallable>,
        parent: Label<out DbStmtparent>,
        idx: Int
    ) {
        with("statement", s) {
            when (s) {
                is IrExpression -> {
                    extractExpressionStmt(s, callable, parent, idx)
                }
                is IrVariable -> {
                    extractVariable(s, callable, parent, idx)
                }
                is IrClass -> {
                    extractLocalTypeDeclStmt(s, callable, parent, idx)
                }
                is IrFunction -> {
                    if (s.isLocalFunction()) {
                        val compilerGeneratedKindOverride =
                            if (s.origin == IrDeclarationOrigin.ADAPTER_FOR_CALLABLE_REFERENCE) {
                                CompilerGeneratedKinds.DECLARING_CLASSES_OF_ADAPTER_FUNCTIONS
                            } else {
                                null
                            }
                        val classId =
                            extractGeneratedClass(
                                s,
                                listOf(pluginContext.irBuiltIns.anyType),
                                compilerGeneratedKindOverride = compilerGeneratedKindOverride
                            )
                        extractLocalTypeDeclStmt(classId, s, callable, parent, idx)
                        val ids = getLocallyVisibleFunctionLabels(s)
                        tw.writeKtLocalFunction(ids.function)
                    } else {
                        logger.errorElement("Expected to find local function", s)
                    }
                }
                is IrLocalDelegatedProperty -> {
                    val blockId = tw.getFreshIdLabel<DbBlock>()
                    val locId = tw.getLocation(s)
                    tw.writeStmts_block(blockId, parent, idx, callable)
                    tw.writeHasLocation(blockId, locId)
                    extractVariable(s.delegate, callable, blockId, 0)

                    val propId = tw.getFreshIdLabel<DbKt_property>()
                    tw.writeKtProperties(propId, s.name.asString())
                    tw.writeHasLocation(propId, locId)
                    tw.writeKtPropertyDelegates(propId, useVariable(s.delegate))

                    // Getter:
                    extractStatement(s.getter, callable, blockId, 1)
                    val getterLabel = getLocallyVisibleFunctionLabels(s.getter).function
                    tw.writeKtPropertyGetters(propId, getterLabel)

                    val setter = s.setter
                    if (setter != null) {
                        extractStatement(setter, callable, blockId, 2)
                        val setterLabel = getLocallyVisibleFunctionLabels(setter).function
                        tw.writeKtPropertySetters(propId, setterLabel)
                    }
                }
                else -> {
                    logger.errorElement("Unrecognised IrStatement: " + s.javaClass, s)
                }
            }
        }
    }

    /**
     * Returns true iff `c` is a call to the function `fName` in the `kotlin.internal.ir` package.
     * This is used to find calls to builtin functions, which need to be handled specially as they
     * do not have corresponding source definitions.
     */
    private fun isBuiltinCallInternal(c: IrCall, fName: String) =
        isBuiltinCall(c, fName, "kotlin.internal.ir")
    /**
     * Returns true iff `c` is a call to the function `fName` in the `kotlin` package. This is used
     * to find calls to builtin functions, which need to be handled specially as they do not have
     * corresponding source definitions.
     */
    private fun isBuiltinCallKotlin(c: IrCall, fName: String) = isBuiltinCall(c, fName, "kotlin")

    /**
     * Returns true iff `c` is a call to the function `fName` in package `pName`. This is used to
     * find calls to builtin functions, which need to be handled specially as they do not have
     * corresponding source definitions.
     */
    private fun isBuiltinCall(c: IrCall, fName: String, pName: String): Boolean {
        val verbose = false
        fun verboseln(s: String) {
            if (verbose) println(s)
        }
        verboseln("Attempting builtin match for $fName")
        val target = c.symbol.owner
        if (target.name.asString() != fName) {
            verboseln("No match as function name is ${target.name.asString()} not $fName")
            return false
        }

        val targetPkg = target.parent
        if (targetPkg !is IrPackageFragment) {
            verboseln("No match as didn't find target package")
            return false
        }
        val targetName = targetPkg.packageFqName.asString()
        if (targetName != pName) {
            verboseln("No match as package name is $targetName")
            return false
        }
        verboseln("Match")
        return true
    }

    private fun unaryOp(
        id: Label<out DbExpr>,
        c: IrCall,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) {
        val locId = tw.getLocation(c)
        extractExprContext(id, locId, callable, enclosingStmt)

        val dr = c.dispatchReceiver
        if (dr != null) {
            logger.errorElement("Unexpected dispatch receiver found", c)
        }

        if (c.valueArgumentsCount < 1) {
            logger.errorElement("No arguments found", c)
            return
        }

        extractArgument(id, c, callable, enclosingStmt, 0, "Operand null")

        if (c.valueArgumentsCount > 1) {
            logger.errorElement("Extra arguments found", c)
        }
    }

    private fun binOp(
        id: Label<out DbExpr>,
        c: IrCall,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) {
        val locId = tw.getLocation(c)
        extractExprContext(id, locId, callable, enclosingStmt)

        val dr = c.dispatchReceiver
        if (dr != null) {
            logger.errorElement("Unexpected dispatch receiver found", c)
        }

        if (c.valueArgumentsCount < 1) {
            logger.errorElement("No arguments found", c)
            return
        }

        extractArgument(id, c, callable, enclosingStmt, 0, "LHS null")

        if (c.valueArgumentsCount < 2) {
            logger.errorElement("No RHS found", c)
            return
        }

        extractArgument(id, c, callable, enclosingStmt, 1, "RHS null")

        if (c.valueArgumentsCount > 2) {
            logger.errorElement("Extra arguments found", c)
        }
    }

    private fun extractArgument(
        id: Label<out DbExpr>,
        c: IrCall,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>,
        idx: Int,
        msg: String
    ) {
        val op = c.getValueArgument(idx)
        if (op == null) {
            logger.errorElement(msg, c)
        } else {
            extractExpressionExpr(op, callable, id, idx, enclosingStmt)
        }
    }

    private fun getDeclaringTypeArguments(
        callTarget: IrFunction,
        receiverType: IrSimpleType
    ): List<IrTypeArgument> {
        val declaringType = callTarget.parentAsClass
        val receiverClass = receiverType.classifier.owner as? IrClass ?: return listOf()
        val ancestorTypes = ArrayList<IrSimpleType>()

        // KFunctionX doesn't implement FunctionX on versions before 1.7.0:
        if (
            (callTarget.name.asString() == "invoke") &&
                (receiverClass.fqNameWhenAvailable
                    ?.asString()
                    ?.startsWith("kotlin.reflect.KFunction") == true) &&
                (callTarget.parentClassOrNull
                    ?.fqNameWhenAvailable
                    ?.asString()
                    ?.startsWith("kotlin.Function") == true)
        ) {
            return receiverType.arguments
        }

        // Populate ancestorTypes with the path from receiverType's class to its ancestor,
        // callTarget's declaring type.
        fun walkFrom(c: IrClass): Boolean {
            if (declaringType == c) return true
            else {
                c.superTypes.forEach {
                    val ancestorClass =
                        (it as? IrSimpleType)?.classifier?.owner as? IrClass ?: return false
                    ancestorTypes.add(it)
                    if (walkFrom(ancestorClass)) return true else ancestorTypes.pop()
                }
                return false
            }
        }

        // If a path was found, repeatedly substitute types to get the corresponding specialisation
        // of that ancestor.
        if (!walkFrom(receiverClass)) {
            logger.errorElement(
                "Failed to find a class declaring ${callTarget.name} starting at ${receiverClass.name}",
                callTarget
            )
            return listOf()
        } else {
            var subbedType: IrSimpleType = receiverType
            ancestorTypes.forEach {
                val thisClass = subbedType.classifier.owner
                if (thisClass !is IrClass) {
                    logger.errorElement(
                        "Found ancestor with unexpected type ${thisClass.javaClass}",
                        callTarget
                    )
                    return listOf()
                }
                val itSubbed =
                    it.substituteTypeArguments(thisClass.typeParameters, subbedType.arguments)
                if (itSubbed !is IrSimpleType) {
                    logger.errorElement(
                        "Substituted type has unexpected type ${itSubbed.javaClass}",
                        callTarget
                    )
                    return listOf()
                }
                subbedType = itSubbed
            }
            return subbedType.arguments
        }
    }

    private fun extractNewExprForLocalFunction(
        ids: LocallyVisibleFunctionLabels,
        parent: Label<out DbExprparent>,
        locId: Label<DbLocation>,
        enclosingCallable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) {

        val idNewexpr =
            extractNewExpr(
                ids.constructor,
                ids.type,
                locId,
                parent,
                -1,
                enclosingCallable,
                enclosingStmt
            )
        extractTypeAccessRecursive(
            pluginContext.irBuiltIns.anyType,
            locId,
            idNewexpr,
            -3,
            enclosingCallable,
            enclosingStmt
        )
    }

    private fun extractMethodAccessWithoutArgs(
        returnType: IrType,
        locId: Label<DbLocation>,
        enclosingCallable: Label<out DbCallable>,
        callsiteParent: Label<out DbExprparent>,
        childIdx: Int,
        enclosingStmt: Label<out DbStmt>,
        methodLabel: Label<out DbCallable>?
    ) =
        tw.getFreshIdLabel<DbMethodaccess>().also { id ->
            val type = useType(returnType)

            tw.writeExprs_methodaccess(id, type.javaResult.id, callsiteParent, childIdx)
            tw.writeExprsKotlinType(id, type.kotlinResult.id)
            extractExprContext(id, locId, enclosingCallable, enclosingStmt)

            // The caller should have warned about this before, so we don't repeat the warning here.
            if (methodLabel != null) tw.writeCallableBinding(id, methodLabel)
        }

    private val defaultConstructorMarkerClass by lazy {
        referenceExternalClass("kotlin.jvm.internal.DefaultConstructorMarker")
    }

    private val defaultConstructorMarkerType by lazy { defaultConstructorMarkerClass?.typeWith() }

    private fun getDefaultsMethodLastArgType(f: IrFunction) =
        (if (f is IrConstructor) defaultConstructorMarkerType else null)
            ?: pluginContext.irBuiltIns.anyType

    private fun getDefaultsMethodArgTypes(f: IrFunction) =
        // The $default method has type ([dispatchReceiver], [extensionReceiver], paramTypes...,
        // int, Object)
        // All parameter types are erased. The trailing int is a mask indicating which parameter
        // values are real
        // and which should be replaced by defaults. The final Object parameter is apparently always
        // null.
        (listOfNotNull(if (f.shouldExtractAsStatic) null else f.dispatchReceiverParameter?.type) +
                listOfNotNull(f.extensionReceiverParameter?.type) +
                f.valueParameters.map { it.type } +
                listOf(pluginContext.irBuiltIns.intType, getDefaultsMethodLastArgType(f)))
            .map { erase(it) }

    private fun getDefaultsMethodName(f: IrFunction) =
        if (f is IrConstructor) {
            f.returnType.let {
                when {
                    it.isAnonymous -> ""
                    else -> it.classFqName?.shortName()?.asString() ?: f.name.asString()
                }
            }
        } else {
            getFunctionShortName(f).nameInDB + "\$default"
        }

    private fun getDefaultsMethodLabel(f: IrFunction): Label<out DbCallable>? {
        val classTypeArgsIncludingOuterClasses = null
        val parentId = useDeclarationParentOf(f, false, classTypeArgsIncludingOuterClasses, true)
        if (parentId == null) {
            logger.errorElement("Couldn't get parent ID for defaults method", f)
            return null
        }
        return getDefaultsMethodLabel(f, parentId)
    }

    private fun getDefaultsMethodLabel(
        f: IrFunction,
        parentId: Label<out DbElement>
    ): Label<out DbCallable> {
        val defaultsMethodName = if (f is IrConstructor) "<init>" else getDefaultsMethodName(f)
        val argTypes = getDefaultsMethodArgTypes(f)

        val defaultMethodLabelStr =
            getFunctionLabel(
                f.parent,
                parentId,
                defaultsMethodName,
                argTypes,
                erase(f.returnType),
                extensionParamType = null, // if there's any, that's included already in argTypes
                functionTypeParameters = listOf(),
                classTypeArgsIncludingOuterClasses = null,
                overridesCollectionsMethod = false,
                javaSignature = null,
                addParameterWildcardsByDefault = false
            )

        return tw.getLabelFor(defaultMethodLabelStr)
    }

    private fun extractsDefaultsCall(
        syntacticCallTarget: IrFunction,
        locId: Label<DbLocation>,
        resultType: IrType,
        enclosingCallable: Label<out DbCallable>,
        callsiteParent: Label<out DbExprparent>,
        childIdx: Int,
        enclosingStmt: Label<out DbStmt>,
        valueArguments: List<IrExpression?>,
        dispatchReceiver: IrExpression?,
        extensionReceiver: IrExpression?
    ) {
        val callTarget = syntacticCallTarget.target.realOverrideTarget
        if (isExternalDeclaration(callTarget)) {
            // Ensure the real target gets extracted, as we might not every directly touch it thanks
            // to this call being redirected to a $default method.
            useFunction<DbCallable>(callTarget)
        }

        // Default parameter values are inherited by overrides; in this case the call should
        // dispatch against the $default method belonging to the class
        // that specified the default values, which will in turn dynamically dispatch back to the
        // relevant override.
        val overriddenCallTarget =
            (callTarget as? IrSimpleFunction)?.allOverriddenIncludingSelf()?.firstOrNull {
                it.overriddenSymbols.isEmpty() &&
                    it.valueParameters.any { p -> p.defaultValue != null }
            } ?: callTarget
        if (isExternalDeclaration(overriddenCallTarget)) {
            // Likewise, ensure the overridden target gets extracted.
            useFunction<DbCallable>(overriddenCallTarget)
        }

        val defaultMethodLabel = getDefaultsMethodLabel(overriddenCallTarget)
        val id =
            extractMethodAccessWithoutArgs(
                resultType,
                locId,
                enclosingCallable,
                callsiteParent,
                childIdx,
                enclosingStmt,
                defaultMethodLabel
            )

        if (overriddenCallTarget.isLocalFunction()) {
            extractTypeAccess(
                getLocallyVisibleFunctionLabels(overriddenCallTarget).type,
                locId,
                id,
                -1,
                enclosingCallable,
                enclosingStmt
            )
        } else {
            extractStaticTypeAccessQualifierUnchecked(
                overriddenCallTarget,
                id,
                locId,
                enclosingCallable,
                enclosingStmt
            )
        }

        extractDefaultsCallArguments(
            id,
            overriddenCallTarget,
            enclosingCallable,
            enclosingStmt,
            valueArguments,
            dispatchReceiver,
            extensionReceiver
        )
    }

    private fun extractDefaultsCallArguments(
        id: Label<out DbExprparent>,
        callTarget: IrFunction,
        enclosingCallable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>,
        valueArguments: List<IrExpression?>,
        dispatchReceiver: IrExpression?,
        extensionReceiver: IrExpression?
    ) {
        var nextIdx = 0
        if (dispatchReceiver != null && !callTarget.shouldExtractAsStatic) {
            extractExpressionExpr(dispatchReceiver, enclosingCallable, id, nextIdx++, enclosingStmt)
        }

        if (extensionReceiver != null) {
            extractExpressionExpr(
                extensionReceiver,
                enclosingCallable,
                id,
                nextIdx++,
                enclosingStmt
            )
        }

        val valueArgsWithDummies =
            valueArguments.zip(callTarget.valueParameters).map { (expr, param) ->
                expr ?: IrConstImpl.defaultValueForType(0, 0, param.type)
            }

        var realParamsMask = 0
        valueArguments.forEachIndexed { index, arg ->
            if (arg != null) realParamsMask = realParamsMask or (1 shl index)
        }

        val extraArgs =
            listOf(
                IrConstImpl.int(0, 0, pluginContext.irBuiltIns.intType, realParamsMask),
                IrConstImpl.defaultValueForType(0, 0, getDefaultsMethodLastArgType(callTarget))
            )

        extractCallValueArguments(
            id,
            valueArgsWithDummies + extraArgs,
            enclosingStmt,
            enclosingCallable,
            nextIdx,
            extractVarargAsArray = true
        )
    }

    private fun getFunctionInvokeMethod(typeArgs: List<IrTypeArgument>): IrFunction? {
        // For `kotlin.FunctionX` and `kotlin.reflect.KFunctionX` interfaces, we're making sure that
        // we
        // extract the call to the `invoke` method that does exist,
        // `kotlin.jvm.functions.FunctionX::invoke`.
        val functionalInterface = getFunctionalInterfaceTypeWithTypeArgs(typeArgs)
        if (functionalInterface == null) {
            logger.warn("Cannot find functional interface type for raw method access")
            return null
        }
        val functionalInterfaceClass = functionalInterface.classOrNull
        if (functionalInterfaceClass == null) {
            logger.warn("Cannot find functional interface class for raw method access")
            return null
        }
        val interfaceType = functionalInterfaceClass.owner
        val substituted = getJavaEquivalentClass(interfaceType) ?: interfaceType
        val function = findFunction(substituted, OperatorNameConventions.INVOKE.asString())
        if (function == null) {
            logger.warn("Cannot find invoke function for raw method access")
            return null
        }
        return function
    }

    private fun isFunctionInvoke(callTarget: IrFunction, drType: IrSimpleType) =
        (drType.isFunctionOrKFunction() || drType.isSuspendFunctionOrKFunction()) &&
            callTarget.name.asString() == OperatorNameConventions.INVOKE.asString()

    private fun getCalleeMethodId(
        callTarget: IrFunction,
        drType: IrType?,
        allowInstantiatedGenericMethod: Boolean
    ): Label<out DbCallable>? {
        if (callTarget.isLocalFunction())
            return getLocallyVisibleFunctionLabels(callTarget).function

        if (
            allowInstantiatedGenericMethod &&
                drType is IrSimpleType &&
                !isUnspecialised(drType, logger)
        ) {
            val calleeIsInvoke = isFunctionInvoke(callTarget, drType)

            val extractionMethod =
                if (calleeIsInvoke) getFunctionInvokeMethod(drType.arguments) else callTarget

            return extractionMethod?.let {
                val typeArgs =
                    if (calleeIsInvoke && drType.arguments.size > BuiltInFunctionArity.BIG_ARITY) {
                        // Big arity `invoke` methods have a special implementation on JVM, they are
                        // transformed to a call to
                        // `kotlin.jvm.functions.FunctionN<out R>::invoke(vararg args: Any?)`, so we
                        // only need to pass the type
                        // argument for the return type. Additionally, the arguments are extracted
                        // inside an array literal below.
                        listOf(drType.arguments.last())
                    } else {
                        getDeclaringTypeArguments(callTarget, drType)
                    }
                useFunction<DbCallable>(extractionMethod, typeArgs)
            }
        } else {
            return useFunction<DbCallable>(callTarget)
        }
    }

    private fun getCalleeRealOverrideTarget(f: IrFunction): IrFunction {
        val target = f.target.realOverrideTarget
        return if (overridesCollectionsMethodWithAlteredParameterTypes(f))
        // Cope with the case where an inherited callee can be rewritten with substituted parameter
        // types
        // if the child class uses it to implement a collections interface
        // (for example, `class A { boolean contains(Object o) { ... } }; class B<T> extends A
        // implements Set<T> { ... }`
        // leads to generating a function `A.contains(B::T)`, with `initialSignatureFunction`
        // pointing to `A.contains(Object)`.
        (target as? IrLazyFunction)?.initialSignatureFunction ?: target
        else target
    }

    private fun callUsesDefaultArguments(
        callTarget: IrFunction,
        valueArguments: List<IrExpression?>
    ): Boolean {
        val varargParam = callTarget.valueParameters.withIndex().find { it.value.isVararg }
        // If the vararg param is the only one not specified, and it has no default value, then we
        // don't need to call a $default method,
        // as omitting it already implies passing an empty vararg array.
        val nullAllowedIdx =
            if (varargParam != null && varargParam.value.defaultValue == null) varargParam.index
            else -1
        return valueArguments.withIndex().any { (index, it) ->
            it == null && index != nullAllowedIdx
        }
    }

    fun extractRawMethodAccess(
        syntacticCallTarget: IrFunction,
        locElement: IrElement,
        resultType: IrType,
        enclosingCallable: Label<out DbCallable>,
        callsiteParent: Label<out DbExprparent>,
        childIdx: Int,
        enclosingStmt: Label<out DbStmt>,
        valueArguments: List<IrExpression?>,
        dispatchReceiver: IrExpression?,
        extensionReceiver: IrExpression?,
        typeArguments: List<IrType> = listOf(),
        extractClassTypeArguments: Boolean = false,
        superQualifierSymbol: IrClassSymbol? = null
    ) {

        val locId = tw.getLocation(locElement)

        if (callUsesDefaultArguments(syntacticCallTarget, valueArguments)) {
            extractsDefaultsCall(
                syntacticCallTarget,
                locId,
                resultType,
                enclosingCallable,
                callsiteParent,
                childIdx,
                enclosingStmt,
                valueArguments,
                dispatchReceiver,
                extensionReceiver
            )
        } else {
            extractRawMethodAccess(
                syntacticCallTarget,
                locId,
                resultType,
                enclosingCallable,
                callsiteParent,
                childIdx,
                enclosingStmt,
                valueArguments.size,
                { argParent, idxOffset ->
                    extractCallValueArguments(
                        argParent,
                        valueArguments,
                        enclosingStmt,
                        enclosingCallable,
                        idxOffset
                    )
                },
                dispatchReceiver?.type,
                dispatchReceiver?.let {
                    { callId ->
                        extractExpressionExpr(
                            dispatchReceiver,
                            enclosingCallable,
                            callId,
                            -1,
                            enclosingStmt
                        )
                    }
                },
                extensionReceiver?.let {
                    { argParent ->
                        extractExpressionExpr(
                            extensionReceiver,
                            enclosingCallable,
                            argParent,
                            0,
                            enclosingStmt
                        )
                    }
                },
                typeArguments,
                extractClassTypeArguments,
                superQualifierSymbol
            )
        }
    }

    fun extractRawMethodAccess(
        syntacticCallTarget: IrFunction,
        locId: Label<DbLocation>,
        returnType: IrType,
        enclosingCallable: Label<out DbCallable>,
        callsiteParent: Label<out DbExprparent>,
        childIdx: Int,
        enclosingStmt: Label<out DbStmt>,
        nValueArguments: Int,
        extractValueArguments: (Label<out DbExpr>, Int) -> Unit,
        drType: IrType?,
        extractDispatchReceiver: ((Label<out DbExpr>) -> Unit)?,
        extractExtensionReceiver: ((Label<out DbExpr>) -> Unit)?,
        typeArguments: List<IrType> = listOf(),
        extractClassTypeArguments: Boolean = false,
        superQualifierSymbol: IrClassSymbol? = null
    ) {

        val callTarget = getCalleeRealOverrideTarget(syntacticCallTarget)
        val methodId = getCalleeMethodId(callTarget, drType, extractClassTypeArguments)
        if (methodId == null) {
            logger.warn("No method to bind call to for raw method access")
        }

        val id =
            extractMethodAccessWithoutArgs(
                returnType,
                locId,
                enclosingCallable,
                callsiteParent,
                childIdx,
                enclosingStmt,
                methodId
            )

        // type arguments at index -2, -3, ...
        extractTypeArguments(typeArguments, locId, id, enclosingCallable, enclosingStmt, -2, true)

        if (callTarget.isLocalFunction()) {
            extractNewExprForLocalFunction(
                getLocallyVisibleFunctionLabels(callTarget),
                id,
                locId,
                enclosingCallable,
                enclosingStmt
            )
        } else if (callTarget.shouldExtractAsStatic) {
            extractStaticTypeAccessQualifier(
                callTarget,
                id,
                locId,
                enclosingCallable,
                enclosingStmt
            )
        } else if (superQualifierSymbol != null) {
            extractSuperAccess(
                superQualifierSymbol.typeWith(),
                enclosingCallable,
                id,
                -1,
                enclosingStmt,
                locId
            )
        } else if (extractDispatchReceiver != null) {
            extractDispatchReceiver(id)
        }

        val idxOffset = if (extractExtensionReceiver != null) 1 else 0

        val isBigArityFunctionInvoke =
            drType is IrSimpleType &&
                isFunctionInvoke(callTarget, drType) &&
                drType.arguments.size > BuiltInFunctionArity.BIG_ARITY

        val argParent =
            if (isBigArityFunctionInvoke) {
                extractArrayCreationWithInitializer(
                    id,
                    nValueArguments + idxOffset,
                    locId,
                    enclosingCallable,
                    enclosingStmt
                )
            } else {
                id
            }

        if (extractExtensionReceiver != null) {
            extractExtensionReceiver(argParent)
        }

        extractValueArguments(argParent, idxOffset)
    }

    private fun extractStaticTypeAccessQualifierUnchecked(
        target: IrDeclaration,
        parentExpr: Label<out DbExprparent>,
        locId: Label<DbLocation>,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?
    ) {
        val parent = target.parent
        if (parent is IrExternalPackageFragment) {
            // This is in a file class.
            val fqName = getFileClassFqName(target)
            if (fqName == null) {
                logger.error(
                    "Can't get FqName for static type access qualifier in external package fragment ${target.javaClass}"
                )
            } else {
                extractTypeAccess(
                    useFileClassType(fqName),
                    locId,
                    parentExpr,
                    -1,
                    enclosingCallable,
                    enclosingStmt
                )
            }
        } else if (parent is IrClass) {
            extractTypeAccessRecursive(
                parent.toRawType(),
                locId,
                parentExpr,
                -1,
                enclosingCallable,
                enclosingStmt
            )
        } else if (parent is IrFile) {
            extractTypeAccess(
                useFileClassType(parent),
                locId,
                parentExpr,
                -1,
                enclosingCallable,
                enclosingStmt
            )
        } else {
            logger.warnElement(
                "Unexpected static type access qualifier ${parent.javaClass}",
                parent
            )
        }
    }

    private fun extractStaticTypeAccessQualifier(
        target: IrDeclaration,
        parentExpr: Label<out DbExprparent>,
        locId: Label<DbLocation>,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?
    ) {
        if (target.shouldExtractAsStatic) {
            extractStaticTypeAccessQualifierUnchecked(
                target,
                parentExpr,
                locId,
                enclosingCallable,
                enclosingStmt
            )
        }
    }

    private fun isStaticAnnotatedNonCompanionMember(f: IrSimpleFunction) =
        f.parentClassOrNull?.isNonCompanionObject == true &&
            (f.hasAnnotation(jvmStaticFqName) ||
                f.correspondingPropertySymbol?.owner?.hasAnnotation(jvmStaticFqName) == true)

    private val IrDeclaration.shouldExtractAsStatic: Boolean
        get() =
            this is IrSimpleFunction &&
                (isStaticFunction(this) || isStaticAnnotatedNonCompanionMember(this)) ||
                this is IrField && this.isStatic ||
                this is IrEnumEntry

    private fun extractCallValueArguments(
        callId: Label<out DbExprparent>,
        call: IrFunctionAccessExpression,
        enclosingStmt: Label<out DbStmt>,
        enclosingCallable: Label<out DbCallable>,
        idxOffset: Int
    ) =
        extractCallValueArguments(
            callId,
            (0 until call.valueArgumentsCount).map { call.getValueArgument(it) },
            enclosingStmt,
            enclosingCallable,
            idxOffset
        )

    private fun extractCallValueArguments(
        callId: Label<out DbExprparent>,
        valueArguments: List<IrExpression?>,
        enclosingStmt: Label<out DbStmt>,
        enclosingCallable: Label<out DbCallable>,
        idxOffset: Int,
        extractVarargAsArray: Boolean = false
    ) {
        var i = 0
        valueArguments.forEach { arg ->
            if (arg != null) {
                if (arg is IrVararg && !extractVarargAsArray) {
                    arg.elements.forEachIndexed { varargNo, vararg ->
                        extractVarargElement(
                            vararg,
                            enclosingCallable,
                            callId,
                            i + idxOffset + varargNo,
                            enclosingStmt
                        )
                    }
                    i += arg.elements.size
                } else {
                    extractExpressionExpr(
                        arg,
                        enclosingCallable,
                        callId,
                        (i++) + idxOffset,
                        enclosingStmt
                    )
                }
            }
        }
    }

    private fun findFunction(cls: IrClass, name: String): IrFunction? =
        cls.declarations.findSubType<IrFunction> { it.name.asString() == name }

    val jvmIntrinsicsClass by lazy { referenceExternalClass("kotlin.jvm.internal.Intrinsics") }

    private fun findJdkIntrinsicOrWarn(name: String, warnAgainstElement: IrElement): IrFunction? {
        val result = jvmIntrinsicsClass?.let { findFunction(it, name) }
        if (result == null) {
            logger.errorElement("Couldn't find JVM intrinsic function $name", warnAgainstElement)
        }
        return result
    }

    private fun findTopLevelFunctionOrWarn(
        functionPkg: String,
        functionName: String,
        type: String,
        parameterTypes: Array<String>,
        warnAgainstElement: IrElement
    ): IrFunction? {

        val fn =
            getFunctionsByFqName(pluginContext, functionPkg, functionName)
                .firstOrNull { fnSymbol ->
                    val owner = fnSymbol.owner
                    (owner.parentClassOrNull?.fqNameWhenAvailable?.asString() == type ||
                        (owner.parent is IrExternalPackageFragment &&
                            getFileClassFqName(owner)?.asString() == type)) &&
                        owner.valueParameters
                            .map { it.type.classFqName?.asString() }
                            .toTypedArray() contentEquals parameterTypes
                }
                ?.owner

        if (fn != null) {
            if (fn.parentClassOrNull != null) {
                extractExternalClassLater(fn.parentAsClass)
            }
        } else {
            logger.errorElement(
                "Couldn't find JVM intrinsic function $functionPkg $functionName in $type",
                warnAgainstElement
            )
        }

        return fn
    }

    private fun findTopLevelPropertyOrWarn(
        propertyPkg: String,
        propertyName: String,
        type: String,
        warnAgainstElement: IrElement
    ): IrProperty? {

        val prop =
            getPropertiesByFqName(pluginContext, propertyPkg, propertyName)
                .firstOrNull { it.owner.parentClassOrNull?.fqNameWhenAvailable?.asString() == type }
                ?.owner

        if (prop != null) {
            if (prop.parentClassOrNull != null) {
                extractExternalClassLater(prop.parentAsClass)
            }
        } else {
            logger.errorElement(
                "Couldn't find JVM intrinsic property $propertyPkg $propertyName in $type",
                warnAgainstElement
            )
        }

        return prop
    }

    val javaLangString by lazy { referenceExternalClass("java.lang.String") }

    val stringValueOfObjectMethod by lazy {
        val result =
            javaLangString?.declarations?.findSubType<IrFunction> {
                it.name.asString() == "valueOf" &&
                    it.valueParameters.size == 1 &&
                    it.valueParameters[0].type == pluginContext.irBuiltIns.anyNType
            }
        if (result == null) {
            logger.error("Couldn't find declaration java.lang.String.valueOf(Object)")
        }
        result
    }

    val objectCloneMethod by lazy {
        val result =
            javaLangObject?.declarations?.findSubType<IrFunction> { it.name.asString() == "clone" }
        if (result == null) {
            logger.error("Couldn't find declaration java.lang.Object.clone(...)")
        }
        result
    }

    val kotlinNoWhenBranchMatchedExn by lazy {
        referenceExternalClass("kotlin.NoWhenBranchMatchedException")
    }

    val kotlinNoWhenBranchMatchedConstructor by lazy {
        val result =
            kotlinNoWhenBranchMatchedExn?.declarations?.findSubType<IrConstructor> {
                it.valueParameters.isEmpty()
            }
        if (result == null) {
            logger.error("Couldn't find no-arg constructor for kotlin.NoWhenBranchMatchedException")
        }
        result
    }

    val javaUtilArrays by lazy { referenceExternalClass("java.util.Arrays") }

    private fun isFunction(
        target: IrFunction,
        pkgName: String,
        classNameLogged: String,
        classNamePredicate: (String) -> Boolean,
        vararg fNames: String,
        isNullable: Boolean? = false
    ) =
        fNames.any {
            isFunction(target, pkgName, classNameLogged, classNamePredicate, it, isNullable)
        }

    private fun isFunction(
        target: IrFunction,
        pkgName: String,
        classNameLogged: String,
        classNamePredicate: (String) -> Boolean,
        fName: String,
        isNullable: Boolean? = false
    ): Boolean {
        val verbose = false
        fun verboseln(s: String) {
            if (verbose) println(s)
        }
        verboseln("Attempting match for $pkgName $classNameLogged $fName")
        if (target.name.asString() != fName) {
            verboseln("No match as function name is ${target.name.asString()} not $fName")
            return false
        }
        val extensionReceiverParameter = target.extensionReceiverParameter
        val targetClass =
            if (extensionReceiverParameter == null) {
                if (isNullable == true) {
                    verboseln(
                        "Nullablility of type didn't match (target is not an extension method)"
                    )
                    return false
                }
                target.parent
            } else {
                val st = extensionReceiverParameter.type as? IrSimpleType
                if (isNullable != null && st?.isNullableCodeQL() != isNullable) {
                    verboseln("Nullablility of type didn't match")
                    return false
                }
                st?.classifier?.owner
            }
        if (targetClass !is IrClass) {
            verboseln("No match as didn't find target class")
            return false
        }
        if (!classNamePredicate(targetClass.name.asString())) {
            verboseln(
                "No match as class name is ${targetClass.name.asString()} not $classNameLogged"
            )
            return false
        }
        val targetPkg = targetClass.parent
        if (targetPkg !is IrPackageFragment) {
            verboseln("No match as didn't find target package")
            return false
        }
        val targetName = targetPkg.packageFqName.asString()
        if (targetName != pkgName) {
            verboseln("No match as package name is $targetName not $pkgName")
            return false
        }
        verboseln("Match")
        return true
    }

    private fun isFunction(
        target: IrFunction,
        pkgName: String,
        className: String,
        fName: String,
        isNullable: Boolean? = false
    ) = isFunction(target, pkgName, className, { it == className }, fName, isNullable)

    private fun isNumericFunction(target: IrFunction, fName: String): Boolean {
        return isFunction(target, "kotlin", "Int", fName) ||
            isFunction(target, "kotlin", "Byte", fName) ||
            isFunction(target, "kotlin", "Short", fName) ||
            isFunction(target, "kotlin", "Long", fName) ||
            isFunction(target, "kotlin", "Float", fName) ||
            isFunction(target, "kotlin", "Double", fName)
    }

    private fun isNumericFunction(target: IrFunction, vararg fNames: String) =
        fNames.any { isNumericFunction(target, it) }

    private fun isArrayType(typeName: String) =
        when (typeName) {
            "Array" -> true
            "IntArray" -> true
            "ByteArray" -> true
            "ShortArray" -> true
            "LongArray" -> true
            "FloatArray" -> true
            "DoubleArray" -> true
            "CharArray" -> true
            "BooleanArray" -> true
            else -> false
        }

    private fun isGenericArrayType(typeName: String) =
        when (typeName) {
            "Array" -> true
            else -> false
        }

    private fun extractCall(
        c: IrCall,
        callable: Label<out DbCallable>,
        stmtExprParent: StmtExprParent
    ) {
        with("call", c) {
            val owner = getBoundSymbolOwner(c.symbol, c) ?: return
            val target = tryReplaceSyntheticFunction(owner)

            // The vast majority of types of call want an expr context, so make one available
            // lazily:
            val exprParent by lazy { stmtExprParent.expr(c, callable) }

            val parent by lazy { exprParent.parent }

            val idx by lazy { exprParent.idx }

            val enclosingStmt by lazy { exprParent.enclosingStmt }

            fun extractMethodAccess(
                syntacticCallTarget: IrFunction,
                extractMethodTypeArguments: Boolean = true,
                extractClassTypeArguments: Boolean = false
            ) {
                val typeArgs =
                    if (extractMethodTypeArguments)
                        (0 until c.typeArgumentsCount)
                            .map { c.getTypeArgument(it) }
                            .requireNoNullsOrNull()
                    else listOf()

                if (typeArgs == null) {
                    logger.warn("Missing type argument in extractMethodAccess")
                    return
                }

                extractRawMethodAccess(
                    syntacticCallTarget,
                    c,
                    c.type,
                    callable,
                    parent,
                    idx,
                    enclosingStmt,
                    (0 until c.valueArgumentsCount).map { c.getValueArgument(it) },
                    c.dispatchReceiver,
                    c.extensionReceiver,
                    typeArgs,
                    extractClassTypeArguments,
                    c.superQualifierSymbol
                )
            }

            fun extractSpecialEnumFunction(fnName: String) {
                if (c.typeArgumentsCount != 1) {
                    logger.errorElement("Expected to find exactly one type argument", c)
                    return
                }

                val enumType = (c.getTypeArgument(0) as? IrSimpleType)?.classifier?.owner
                if (enumType == null) {
                    logger.errorElement("Couldn't find type of enum type", c)
                    return
                }

                if (enumType is IrClass) {
                    val func =
                        enumType.declarations.findSubType<IrFunction> {
                            it.name.asString() == fnName
                        }
                    if (func == null) {
                        logger.errorElement("Couldn't find function $fnName on enum type", c)
                        return
                    }

                    extractMethodAccess(func, false)
                } else if (enumType is IrTypeParameter && enumType.isReified) {
                    // A call to `enumValues<T>()` is being extracted, where `T` is a reified type
                    // parameter of an `inline` function.
                    // We can't generate a valid expression here, because we would need to know the
                    // type of T on the call site.
                    // TODO: replace error expression with something that better shows this
                    // expression is unrepresentable.
                    val id = tw.getFreshIdLabel<DbErrorexpr>()
                    val type = useType(c.type)

                    tw.writeExprs_errorexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, tw.getLocation(c), callable, enclosingStmt)
                } else {
                    logger.errorElement("Unexpected enum type rep ${enumType.javaClass}", c)
                }
            }

            fun binopReceiver(
                id: Label<out DbExpr>,
                receiver: IrExpression?,
                receiverDescription: String
            ) {
                extractExprContext(id, tw.getLocation(c), callable, enclosingStmt)

                if (receiver == null) {
                    logger.errorElement("$receiverDescription not found", c)
                } else {
                    extractExpressionExpr(receiver, callable, id, 0, enclosingStmt)
                }
                if (c.valueArgumentsCount < 1) {
                    logger.errorElement("No RHS found", c)
                } else {
                    if (c.valueArgumentsCount > 1) {
                        logger.errorElement("Extra arguments found", c)
                    }
                    val arg = c.getValueArgument(0)
                    if (arg == null) {
                        logger.errorElement("RHS null", c)
                    } else {
                        extractExpressionExpr(arg, callable, id, 1, enclosingStmt)
                    }
                }
            }

            fun unaryopReceiver(
                id: Label<out DbExpr>,
                receiver: IrExpression?,
                receiverDescription: String
            ) {
                extractExprContext(id, tw.getLocation(c), callable, enclosingStmt)

                if (receiver == null) {
                    logger.errorElement("$receiverDescription not found", c)
                } else {
                    extractExpressionExpr(receiver, callable, id, 0, enclosingStmt)
                }
                if (c.valueArgumentsCount > 0) {
                    logger.errorElement("Extra arguments found", c)
                }
            }

            /**
             * Populate the lhs of a binary op from this call's dispatch receiver, and the rhs from
             * its sole argument.
             */
            fun binopDisp(id: Label<out DbExpr>) {
                binopReceiver(id, c.dispatchReceiver, "Dispatch receiver")
            }

            fun binopExt(id: Label<out DbExpr>) {
                binopReceiver(id, c.extensionReceiver, "Extension receiver")
            }

            fun unaryopDisp(id: Label<out DbExpr>) {
                unaryopReceiver(id, c.dispatchReceiver, "Dispatch receiver")
            }

            fun unaryopExt(id: Label<out DbExpr>) {
                unaryopReceiver(id, c.extensionReceiver, "Extension receiver")
            }

            val dr = c.dispatchReceiver
            when {
                isFunction(target, "kotlin", "String", "plus", false) -> {
                    val id = tw.getFreshIdLabel<DbAddexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_addexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binopDisp(id)
                }
                isFunction(target, "kotlin", "String", "plus", true) -> {
                    findJdkIntrinsicOrWarn("stringPlus", c)?.let { stringPlusFn ->
                        extractRawMethodAccess(
                            stringPlusFn,
                            c,
                            c.type,
                            callable,
                            parent,
                            idx,
                            enclosingStmt,
                            listOf(c.extensionReceiver, c.getValueArgument(0)),
                            null,
                            null
                        )
                    }
                }
                isNumericFunction(
                    target,
                    "plus",
                    "minus",
                    "times",
                    "div",
                    "rem",
                    "and",
                    "or",
                    "xor",
                    "shl",
                    "shr",
                    "ushr"
                ) -> {
                    val type = useType(c.type)
                    val id: Label<out DbExpr> =
                        when (val targetName = target.name.asString()) {
                            "plus" -> {
                                val id = tw.getFreshIdLabel<DbAddexpr>()
                                tw.writeExprs_addexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "minus" -> {
                                val id = tw.getFreshIdLabel<DbSubexpr>()
                                tw.writeExprs_subexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "times" -> {
                                val id = tw.getFreshIdLabel<DbMulexpr>()
                                tw.writeExprs_mulexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "div" -> {
                                val id = tw.getFreshIdLabel<DbDivexpr>()
                                tw.writeExprs_divexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "rem" -> {
                                val id = tw.getFreshIdLabel<DbRemexpr>()
                                tw.writeExprs_remexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "and" -> {
                                val id = tw.getFreshIdLabel<DbAndbitexpr>()
                                tw.writeExprs_andbitexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "or" -> {
                                val id = tw.getFreshIdLabel<DbOrbitexpr>()
                                tw.writeExprs_orbitexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "xor" -> {
                                val id = tw.getFreshIdLabel<DbXorbitexpr>()
                                tw.writeExprs_xorbitexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "shl" -> {
                                val id = tw.getFreshIdLabel<DbLshiftexpr>()
                                tw.writeExprs_lshiftexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "shr" -> {
                                val id = tw.getFreshIdLabel<DbRshiftexpr>()
                                tw.writeExprs_rshiftexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "ushr" -> {
                                val id = tw.getFreshIdLabel<DbUrshiftexpr>()
                                tw.writeExprs_urshiftexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            else -> {
                                logger.errorElement("Unhandled binary target name: $targetName", c)
                                return
                            }
                        }
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    if (
                        isFunction(
                            target,
                            "kotlin",
                            "Byte or Short",
                            { it == "Byte" || it == "Short" },
                            "and",
                            "or",
                            "xor"
                        )
                    )
                        binopExt(id)
                    else binopDisp(id)
                }
                // != gets desugared into not and ==. Here we resugar it.
                c.origin == IrStatementOrigin.EXCLEQ &&
                    isFunction(target, "kotlin", "Boolean", "not") &&
                    c.valueArgumentsCount == 0 &&
                    dr != null &&
                    dr is IrCall &&
                    isBuiltinCallInternal(dr, "EQEQ") -> {
                    val id = tw.getFreshIdLabel<DbValueneexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_valueneexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, dr, callable, enclosingStmt)
                }
                c.origin == IrStatementOrigin.EXCLEQEQ &&
                    isFunction(target, "kotlin", "Boolean", "not") &&
                    c.valueArgumentsCount == 0 &&
                    dr != null &&
                    dr is IrCall &&
                    isBuiltinCallInternal(dr, "EQEQEQ") -> {
                    val id = tw.getFreshIdLabel<DbNeexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_neexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, dr, callable, enclosingStmt)
                }
                c.origin == IrStatementOrigin.EXCLEQ &&
                    isFunction(target, "kotlin", "Boolean", "not") &&
                    c.valueArgumentsCount == 0 &&
                    dr != null &&
                    dr is IrCall &&
                    isBuiltinCallInternal(dr, "ieee754equals") -> {
                    val id = tw.getFreshIdLabel<DbNeexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_neexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, dr, callable, enclosingStmt)
                }
                isFunction(target, "kotlin", "Boolean", "not") -> {
                    val id = tw.getFreshIdLabel<DbLognotexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_lognotexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    unaryopDisp(id)
                }
                isNumericFunction(target, "inv", "unaryMinus", "unaryPlus") -> {
                    val type = useType(c.type)
                    val id: Label<out DbExpr> =
                        when (val targetName = target.name.asString()) {
                            "inv" -> {
                                val id = tw.getFreshIdLabel<DbBitnotexpr>()
                                tw.writeExprs_bitnotexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "unaryMinus" -> {
                                val id = tw.getFreshIdLabel<DbMinusexpr>()
                                tw.writeExprs_minusexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            "unaryPlus" -> {
                                val id = tw.getFreshIdLabel<DbPlusexpr>()
                                tw.writeExprs_plusexpr(id, type.javaResult.id, parent, idx)
                                id
                            }
                            else -> {
                                logger.errorElement("Unhandled unary target name: $targetName", c)
                                return
                            }
                        }
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    if (
                        isFunction(
                            target,
                            "kotlin",
                            "Byte or Short",
                            { it == "Byte" || it == "Short" },
                            "inv"
                        )
                    )
                        unaryopExt(id)
                    else unaryopDisp(id)
                }
                // We need to handle all the builtin operators defines in BuiltInOperatorNames in
                //     compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/IrBuiltIns.kt
                // as they can't be extracted as external dependencies.
                isBuiltinCallInternal(c, "less") -> {
                    if (c.origin != IrStatementOrigin.LT) {
                        logger.warnElement("Unexpected origin for LT: ${c.origin}", c)
                    }
                    val id = tw.getFreshIdLabel<DbLtexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_ltexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "lessOrEqual") -> {
                    if (c.origin != IrStatementOrigin.LTEQ) {
                        logger.warnElement("Unexpected origin for LTEQ: ${c.origin}", c)
                    }
                    val id = tw.getFreshIdLabel<DbLeexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_leexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "greater") -> {
                    if (c.origin != IrStatementOrigin.GT) {
                        logger.warnElement("Unexpected origin for GT: ${c.origin}", c)
                    }
                    val id = tw.getFreshIdLabel<DbGtexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_gtexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "greaterOrEqual") -> {
                    if (c.origin != IrStatementOrigin.GTEQ) {
                        logger.warnElement("Unexpected origin for GTEQ: ${c.origin}", c)
                    }
                    val id = tw.getFreshIdLabel<DbGeexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_geexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "EQEQ") -> {
                    if (c.origin != IrStatementOrigin.EQEQ) {
                        logger.warnElement("Unexpected origin for EQEQ: ${c.origin}", c)
                    }
                    val id = tw.getFreshIdLabel<DbValueeqexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_valueeqexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "EQEQEQ") -> {
                    if (c.origin != IrStatementOrigin.EQEQEQ) {
                        logger.warnElement("Unexpected origin for EQEQEQ: ${c.origin}", c)
                    }
                    val id = tw.getFreshIdLabel<DbEqexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_eqexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "ieee754equals") -> {
                    if (c.origin != IrStatementOrigin.EQEQ) {
                        logger.warnElement("Unexpected origin for ieee754equals: ${c.origin}", c)
                    }
                    val id = tw.getFreshIdLabel<DbEqexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_eqexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "CHECK_NOT_NULL") -> {
                    if (c.origin != IrStatementOrigin.EXCLEXCL) {
                        logger.warnElement("Unexpected origin for CHECK_NOT_NULL: ${c.origin}", c)
                    }

                    val id = tw.getFreshIdLabel<DbNotnullexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_notnullexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    unaryOp(id, c, callable, enclosingStmt)
                }
                isBuiltinCallInternal(c, "THROW_CCE") -> {
                    // TODO
                    logger.errorElement("Unhandled builtin", c)
                }
                isBuiltinCallInternal(c, "THROW_ISE") -> {
                    // TODO
                    logger.errorElement("Unhandled builtin", c)
                }
                isBuiltinCallInternal(c, "noWhenBranchMatchedException") -> {
                    kotlinNoWhenBranchMatchedConstructor?.let {
                        val locId = tw.getLocation(c)
                        val thrownType = useSimpleTypeClass(it.parentAsClass, listOf(), false)
                        val stmtParent = stmtExprParent.stmt(c, callable)
                        val throwId = tw.getFreshIdLabel<DbThrowstmt>()
                        tw.writeStmts_throwstmt(
                            throwId,
                            stmtParent.parent,
                            stmtParent.idx,
                            callable
                        )
                        tw.writeHasLocation(throwId, locId)
                        val newExprId =
                            extractNewExpr(
                                it,
                                null,
                                thrownType,
                                locId,
                                throwId,
                                0,
                                callable,
                                throwId
                            )
                        if (newExprId == null) {
                            logger.errorElement(
                                "No ID for newExpr in noWhenBranchMatchedException",
                                c
                            )
                        } else {
                            extractTypeAccess(thrownType, locId, newExprId, -3, callable, throwId)
                        }
                    }
                }
                isBuiltinCallInternal(c, "illegalArgumentException") -> {
                    // TODO
                    logger.errorElement("Unhandled builtin", c)
                }
                isBuiltinCallInternal(c, "ANDAND") -> {
                    // TODO
                    logger.errorElement("Unhandled builtin", c)
                }
                isBuiltinCallInternal(c, "OROR") -> {
                    // TODO
                    logger.errorElement("Unhandled builtin", c)
                }
                isFunction(target, "kotlin", "Any", "toString", true) -> {
                    stringValueOfObjectMethod?.let {
                        extractRawMethodAccess(
                            it,
                            c,
                            c.type,
                            callable,
                            parent,
                            idx,
                            enclosingStmt,
                            listOf(c.extensionReceiver),
                            null,
                            null
                        )
                    }
                }
                isBuiltinCallKotlin(c, "enumValues") -> {
                    extractSpecialEnumFunction("values")
                }
                isBuiltinCallKotlin(c, "enumValueOf") -> {
                    extractSpecialEnumFunction("valueOf")
                }
                isBuiltinCallKotlin(c, "arrayOfNulls") -> {
                    val id = tw.getFreshIdLabel<DbArraycreationexpr>()
                    val type = useType(c.type)
                    tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    val locId = tw.getLocation(c)
                    extractExprContext(id, locId, callable, enclosingStmt)

                    if (c.typeArgumentsCount == 1) {
                        val typeArgument = c.getTypeArgument(0)
                        if (typeArgument == null) {
                            logger.errorElement("Type argument missing in an arrayOfNulls call", c)
                        } else {
                            extractTypeAccessRecursive(
                                typeArgument,
                                locId,
                                id,
                                -1,
                                callable,
                                enclosingStmt,
                                TypeContext.GENERIC_ARGUMENT
                            )
                        }
                    } else {
                        logger.errorElement(
                            "Expected to find exactly one type argument in an arrayOfNulls call",
                            c
                        )
                    }

                    if (c.valueArgumentsCount == 1) {
                        val dim = c.getValueArgument(0)
                        if (dim != null) {
                            extractExpressionExpr(dim, callable, id, 0, enclosingStmt)
                        } else {
                            logger.errorElement(
                                "Expected to find non-null argument in an arrayOfNulls call",
                                c
                            )
                        }
                    } else {
                        logger.errorElement(
                            "Expected to find only one argument in an arrayOfNulls call",
                            c
                        )
                    }
                }
                isBuiltinCallKotlin(c, "arrayOf") ||
                    isBuiltinCallKotlin(c, "doubleArrayOf") ||
                    isBuiltinCallKotlin(c, "floatArrayOf") ||
                    isBuiltinCallKotlin(c, "longArrayOf") ||
                    isBuiltinCallKotlin(c, "intArrayOf") ||
                    isBuiltinCallKotlin(c, "charArrayOf") ||
                    isBuiltinCallKotlin(c, "shortArrayOf") ||
                    isBuiltinCallKotlin(c, "byteArrayOf") ||
                    isBuiltinCallKotlin(c, "booleanArrayOf") -> {

                    val isPrimitiveArrayCreation = !isBuiltinCallKotlin(c, "arrayOf")
                    val elementType =
                        if (isPrimitiveArrayCreation) {
                            c.type.getArrayElementTypeCodeQL(pluginContext.irBuiltIns)
                        } else {
                            // TODO: is there any reason not to always use getArrayElementTypeCodeQL?
                            if (c.typeArgumentsCount == 1) {
                                c.getTypeArgument(0).also {
                                    if (it == null) {
                                        logger.errorElement(
                                            "Type argument missing in an arrayOf call",
                                            c
                                        )
                                    }
                                }
                            } else {
                                logger.errorElement(
                                    "Expected to find one type argument in arrayOf call",
                                    c
                                )
                                null
                            }
                        }

                    val arg =
                        if (c.valueArgumentsCount == 1) c.getValueArgument(0)
                            else {
                                logger.errorElement(
                                    "Expected to find only one (vararg) argument in ${c.symbol.owner.name.asString()} call",
                                    c
                                )
                                null
                            }
                            ?.let {
                                if (it is IrVararg) it
                                else {
                                    logger.errorElement(
                                        "Expected to find vararg argument in ${c.symbol.owner.name.asString()} call",
                                        c
                                    )
                                    null
                                }
                            }

                    extractArrayCreation(
                        arg,
                        c.type,
                        elementType,
                        isPrimitiveArrayCreation,
                        c,
                        parent,
                        idx,
                        callable,
                        enclosingStmt
                    )
                }
                isBuiltinCall(c, "<get-java>", "kotlin.jvm") -> {
                    // Special case for KClass<*>.java, which is used in the Parcelize plugin. In
                    // normal cases, this is already rewritten to the property referenced below:
                    findTopLevelPropertyOrWarn(
                            "kotlin.jvm",
                            "java",
                            "kotlin.jvm.JvmClassMappingKt",
                            c
                        )
                        ?.let { javaProp ->
                            val getter = javaProp.getter
                            if (getter == null) {
                                logger.error(
                                    "Couldn't find getter of `kotlin.jvm.JvmClassMappingKt::java`"
                                )
                                return
                            }

                            val ext = c.extensionReceiver
                            if (ext == null) {
                                logger.errorElement(
                                    "No extension receiver found for `KClass::java` call",
                                    c
                                )
                                return
                            }

                            val argType =
                                (ext.type as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull
                            val typeArguments = if (argType == null) listOf() else listOf(argType)

                            extractRawMethodAccess(
                                getter,
                                c,
                                c.type,
                                callable,
                                parent,
                                idx,
                                enclosingStmt,
                                listOf(),
                                null,
                                ext,
                                typeArguments
                            )
                        }
                }
                isFunction(
                    target,
                    "kotlin",
                    "(some array type)",
                    { isArrayType(it) },
                    "iterator"
                ) -> {
                    val parentClass = target.parent
                    if (parentClass !is IrClass) {
                        logger.errorElement("Iterator parent is not a class", c)
                        return
                    }

                    var typeFilter =
                        if (isGenericArrayType(parentClass.name.asString())) {
                            "kotlin.jvm.internal.ArrayIteratorKt"
                        } else {
                            "kotlin.jvm.internal.ArrayIteratorsKt"
                        }

                    findTopLevelFunctionOrWarn(
                            "kotlin.jvm.internal",
                            "iterator",
                            typeFilter,
                            arrayOf(parentClass.kotlinFqName.asString()),
                            c
                        )
                        ?.let { iteratorFn ->
                            val dispatchReceiver = c.dispatchReceiver
                            if (dispatchReceiver == null) {
                                logger.errorElement(
                                    "No dispatch receiver found for array iterator call",
                                    c
                                )
                            } else {
                                val drType = dispatchReceiver.type
                                if (drType !is IrSimpleType) {
                                    logger.errorElement(
                                        "Dispatch receiver with unexpected type rep found for array iterator call: ${drType.javaClass}",
                                        c
                                    )
                                } else {
                                    val typeArgs =
                                        drType.arguments.map {
                                            when (it) {
                                                is IrTypeProjection -> it.type
                                                else -> pluginContext.irBuiltIns.anyNType
                                            }
                                        }
                                    extractRawMethodAccess(
                                        iteratorFn,
                                        c,
                                        c.type,
                                        callable,
                                        parent,
                                        idx,
                                        enclosingStmt,
                                        listOf(c.dispatchReceiver),
                                        null,
                                        null,
                                        typeArgs
                                    )
                                }
                            }
                        }
                }
                isFunction(target, "kotlin", "(some array type)", { isArrayType(it) }, "get") &&
                    c.origin == IrStatementOrigin.GET_ARRAY_ELEMENT &&
                    c.dispatchReceiver != null -> {
                    val id = tw.getFreshIdLabel<DbArrayaccess>()
                    val type = useType(c.type)
                    tw.writeExprs_arrayaccess(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    binopDisp(id)
                }
                isFunction(target, "kotlin", "(some array type)", { isArrayType(it) }, "set") &&
                    c.origin == IrStatementOrigin.EQ &&
                    c.dispatchReceiver != null -> {
                    val array = c.dispatchReceiver
                    val arrayIdx = c.getValueArgument(0)
                    val assignedValue = c.getValueArgument(1)

                    if (array != null && arrayIdx != null && assignedValue != null) {

                        val locId = tw.getLocation(c)
                        extractAssignExpr(c.type, locId, parent, idx, callable, enclosingStmt)
                            .also { assignId ->
                                tw.getFreshIdLabel<DbArrayaccess>().also { arrayAccessId ->
                                    val arrayType = useType(array.type)
                                    tw.writeExprs_arrayaccess(
                                        arrayAccessId,
                                        arrayType.javaResult.id,
                                        assignId,
                                        0
                                    )
                                    tw.writeExprsKotlinType(
                                        arrayAccessId,
                                        arrayType.kotlinResult.id
                                    )
                                    extractExprContext(
                                        arrayAccessId,
                                        locId,
                                        callable,
                                        enclosingStmt
                                    )

                                    extractExpressionExpr(
                                        array,
                                        callable,
                                        arrayAccessId,
                                        0,
                                        enclosingStmt
                                    )
                                    extractExpressionExpr(
                                        arrayIdx,
                                        callable,
                                        arrayAccessId,
                                        1,
                                        enclosingStmt
                                    )
                                }
                                extractExpressionExpr(
                                    assignedValue,
                                    callable,
                                    assignId,
                                    1,
                                    enclosingStmt
                                )
                            }
                    } else {
                        logger.errorElement("Unexpected Array.set function signature", c)
                    }
                }
                isBuiltinCall(c, "<unsafe-coerce>", "kotlin.jvm.internal") -> {

                    if (c.valueArgumentsCount != 1) {
                        logger.errorElement(
                            "Expected to find one argument for a kotlin.jvm.internal.<unsafe-coerce>() call, but found ${c.valueArgumentsCount}",
                            c
                        )
                        return
                    }

                    if (c.typeArgumentsCount != 2) {
                        logger.errorElement(
                            "Expected to find two type arguments for a kotlin.jvm.internal.<unsafe-coerce>() call, but found ${c.typeArgumentsCount}",
                            c
                        )
                        return
                    }
                    val valueArg = c.getValueArgument(0)
                    if (valueArg == null) {
                        logger.errorElement(
                            "Cannot find value argument for a kotlin.jvm.internal.<unsafe-coerce>() call",
                            c
                        )
                        return
                    }
                    val typeArg = c.getTypeArgument(1)
                    if (typeArg == null) {
                        logger.errorElement(
                            "Cannot find type argument for a kotlin.jvm.internal.<unsafe-coerce>() call",
                            c
                        )
                        return
                    }

                    val id = tw.getFreshIdLabel<DbUnsafecoerceexpr>()
                    val locId = tw.getLocation(c)
                    val type = useType(c.type)
                    tw.writeExprs_unsafecoerceexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractTypeAccessRecursive(typeArg, locId, id, 0, callable, enclosingStmt)
                    extractExpressionExpr(valueArg, callable, id, 1, enclosingStmt)
                }
                isBuiltinCallInternal(c, "dataClassArrayMemberToString") -> {
                    val arrayArg = c.getValueArgument(0)
                    val realArrayClass = arrayArg?.type?.classOrNull
                    if (realArrayClass == null) {
                        logger.errorElement(
                            "Argument to dataClassArrayMemberToString not a class",
                            c
                        )
                        return
                    }
                    val realCallee =
                        javaUtilArrays?.declarations?.findSubType<IrFunction> { decl ->
                            decl.name.asString() == "toString" &&
                                decl.valueParameters.size == 1 &&
                                decl.valueParameters[0].type.classOrNull?.let {
                                    it == realArrayClass
                                } == true
                        }
                    if (realCallee == null) {
                        logger.errorElement(
                            "Couldn't find a java.lang.Arrays.toString method matching class ${realArrayClass.owner.name}",
                            c
                        )
                    } else {
                        extractRawMethodAccess(
                            realCallee,
                            c,
                            c.type,
                            callable,
                            parent,
                            idx,
                            enclosingStmt,
                            listOf(arrayArg),
                            null,
                            null
                        )
                    }
                }
                isBuiltinCallInternal(c, "dataClassArrayMemberHashCode") -> {
                    val arrayArg = c.getValueArgument(0)
                    val realArrayClass = arrayArg?.type?.classOrNull
                    if (realArrayClass == null) {
                        logger.errorElement(
                            "Argument to dataClassArrayMemberHashCode not a class",
                            c
                        )
                        return
                    }
                    val realCallee =
                        javaUtilArrays?.declarations?.findSubType<IrFunction> { decl ->
                            decl.name.asString() == "hashCode" &&
                                decl.valueParameters.size == 1 &&
                                decl.valueParameters[0].type.classOrNull?.let {
                                    it == realArrayClass
                                } == true
                        }
                    if (realCallee == null) {
                        logger.errorElement(
                            "Couldn't find a java.lang.Arrays.hashCode method matching class ${realArrayClass.owner.name}",
                            c
                        )
                    } else {
                        extractRawMethodAccess(
                            realCallee,
                            c,
                            c.type,
                            callable,
                            parent,
                            idx,
                            enclosingStmt,
                            listOf(arrayArg),
                            null,
                            null
                        )
                    }
                }
                else -> {
                    extractMethodAccess(target, true, true)
                }
            }
        }
    }

    private fun extractArrayCreation(
        elementList: IrVararg?,
        resultType: IrType,
        elementType: IrType?,
        allowPrimitiveElementType: Boolean,
        locElement: IrElement,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingCallable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) {
        // If this is [someType]ArrayOf(*x), x, otherwise null
        val clonedArray =
            elementList?.let {
                if (it.elements.size == 1) {
                    val onlyElement = it.elements[0]
                    if (onlyElement is IrSpreadElement) onlyElement.expression else null
                } else null
            }

        if (clonedArray != null) {
            // This is an array clone: extract is as a call to java.lang.Object.clone
            objectCloneMethod?.let {
                extractRawMethodAccess(
                    it,
                    locElement,
                    resultType,
                    enclosingCallable,
                    parent,
                    idx,
                    enclosingStmt,
                    listOf(),
                    clonedArray,
                    null
                )
            }
        } else {
            // This is array creation: extract it as a call to new ArrayType[] { ... }
            val id = tw.getFreshIdLabel<DbArraycreationexpr>()
            val type = useType(resultType)
            tw.writeExprs_arraycreationexpr(id, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(id, type.kotlinResult.id)
            val locId = tw.getLocation(locElement)
            extractExprContext(id, locId, enclosingCallable, enclosingStmt)

            if (elementType != null) {
                val typeContext =
                    if (allowPrimitiveElementType) TypeContext.OTHER
                    else TypeContext.GENERIC_ARGUMENT
                extractTypeAccessRecursive(
                    elementType,
                    locId,
                    id,
                    -1,
                    enclosingCallable,
                    enclosingStmt,
                    typeContext
                )
            }

            if (elementList != null) {
                val initId = tw.getFreshIdLabel<DbArrayinit>()
                tw.writeExprs_arrayinit(initId, type.javaResult.id, id, -2)
                tw.writeExprsKotlinType(initId, type.kotlinResult.id)
                extractExprContext(initId, locId, enclosingCallable, enclosingStmt)
                elementList.elements.forEachIndexed { i, arg ->
                    extractVarargElement(arg, enclosingCallable, initId, i, enclosingStmt)
                }

                extractConstantInteger(
                    elementList.elements.size,
                    locId,
                    id,
                    0,
                    enclosingCallable,
                    enclosingStmt
                )
            }
        }
    }

    private fun extractNewExpr(
        methodId: Label<out DbConstructor>,
        constructedType: TypeResults,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ): Label<DbNewexpr> {
        val id = tw.getFreshIdLabel<DbNewexpr>()
        tw.writeExprs_newexpr(id, constructedType.javaResult.id, parent, idx)
        tw.writeExprsKotlinType(id, constructedType.kotlinResult.id)
        extractExprContext(id, locId, callable, enclosingStmt)
        tw.writeCallableBinding(id, methodId)
        return id
    }

    private fun extractNewExpr(
        calledConstructor: IrFunction,
        constructorTypeArgs: List<IrTypeArgument>?,
        constructedType: TypeResults,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ): Label<DbNewexpr>? {
        val funId = useFunction<DbConstructor>(calledConstructor, constructorTypeArgs)
        if (funId == null) {
            logger.error("Cannot get ID for newExpr function")
            return null
        }
        return extractNewExpr(funId, constructedType, locId, parent, idx, callable, enclosingStmt)
    }

    private fun needsObinitFunction(c: IrClass) =
        c.primaryConstructor == null && c.constructors.count() > 1

    private fun getObinitLabel(c: IrClass, parentId: Label<out DbElement>): String =
        getFunctionLabel(
            c,
            parentId,
            "<obinit>",
            listOf(),
            pluginContext.irBuiltIns.unitType,
            null,
            functionTypeParameters = listOf(),
            classTypeArgsIncludingOuterClasses = listOf(),
            overridesCollectionsMethod = false,
            javaSignature = null,
            addParameterWildcardsByDefault = false
        )

    private fun extractConstructorCall(
        e: IrFunctionAccessExpression,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) {
        val eType = e.type
        if (eType !is IrSimpleType) {
            logger.errorElement("Constructor call has non-simple type ${eType.javaClass}", e)
            return
        }
        val type = useType(eType)
        val isAnonymous = eType.isAnonymous
        val locId = tw.getLocation(e)
        val valueArgs = (0 until e.valueArgumentsCount).map { e.getValueArgument(it) }

        val id =
            if (
                e !is IrEnumConstructorCall && callUsesDefaultArguments(e.symbol.owner, valueArgs)
            ) {
                val defaultsMethodId = getDefaultsMethodLabel(e.symbol.owner)
                if (defaultsMethodId == null) {
                    logger.errorElement("Cannot get defaults method ID", e)
                    return
                }
                extractNewExpr(
                        defaultsMethodId.cast(),
                        type,
                        locId,
                        parent,
                        idx,
                        callable,
                        enclosingStmt
                    )
                    .also {
                        extractDefaultsCallArguments(
                            it,
                            e.symbol.owner,
                            callable,
                            enclosingStmt,
                            valueArgs,
                            null,
                            null
                        )
                    }
            } else {
                val newExprId =
                    extractNewExpr(
                        e.symbol.owner,
                        eType.arguments,
                        type,
                        locId,
                        parent,
                        idx,
                        callable,
                        enclosingStmt
                    )
                if (newExprId == null) {
                    logger.errorElement("Cannot get newExpr ID", e)
                    return
                }

                val realCallTarget = e.symbol.owner.realOverrideTarget
                // Generated constructor calls to kotlin.Enum have no arguments in IR, but the
                // constructor takes two parameters.
                if (
                    e is IrEnumConstructorCall &&
                        realCallTarget is IrConstructor &&
                        realCallTarget.parentClassOrNull?.fqNameWhenAvailable?.asString() ==
                            "kotlin.Enum" &&
                        realCallTarget.valueParameters.size == 2 &&
                        realCallTarget.valueParameters[0].type ==
                            pluginContext.irBuiltIns.stringType &&
                        realCallTarget.valueParameters[1].type == pluginContext.irBuiltIns.intType
                ) {

                    val id0 =
                        extractNull(
                            pluginContext.irBuiltIns.stringType,
                            locId,
                            newExprId,
                            0,
                            callable,
                            enclosingStmt
                        )
                    tw.writeCompiler_generated(
                        id0,
                        CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind
                    )

                    val id1 =
                        extractConstantInteger(0, locId, newExprId, 1, callable, enclosingStmt)
                    tw.writeCompiler_generated(
                        id1,
                        CompilerGeneratedKinds.ENUM_CONSTRUCTOR_ARGUMENT.kind
                    )
                } else {
                    extractCallValueArguments(newExprId, e, enclosingStmt, callable, 0)
                }

                newExprId
            }

        if (isAnonymous) {
            tw.writeIsAnonymClass(type.javaResult.id.cast(), id)
        }

        val dr = e.dispatchReceiver
        if (dr != null) {
            extractExpressionExpr(dr, callable, id, -2, enclosingStmt)
        }

        val typeAccessType =
            if (isAnonymous) {
                val c = eType.classifier.owner
                if (c !is IrClass) {
                    logger.warnElement("Anonymous type not a class (${c.javaClass})", e)
                }
                if ((c as? IrClass)?.superTypes?.size == 1) {
                    useType(c.superTypes.first())
                } else {
                    useType(pluginContext.irBuiltIns.anyType)
                }
            } else {
                type
            }

        if (e is IrConstructorCall) {
            extractConstructorTypeAccess(
                eType,
                typeAccessType,
                e.symbol,
                locId,
                id,
                -3,
                callable,
                enclosingStmt
            )
        } else if (e is IrEnumConstructorCall) {
            val enumClass = e.symbol.owner.parent as? IrClass
            if (enumClass == null) {
                logger.warnElement("Couldn't find declaring class of enum constructor call", e)
                return
            }

            val args =
                (0 until e.typeArgumentsCount).map { e.getTypeArgument(it) }.requireNoNullsOrNull()
            if (args == null) {
                logger.warnElement("Found null type argument in enum constructor call", e)
                return
            }

            val enumType = enumClass.typeWith(args)
            extractConstructorTypeAccess(
                enumType,
                useType(enumType),
                e.symbol,
                locId,
                id,
                -3,
                callable,
                enclosingStmt
            )
        } else {
            logger.errorElement("Unexpected constructor call type: ${e.javaClass}", e)
        }
    }

    abstract inner class StmtExprParent {
        abstract fun stmt(e: IrExpression, callable: Label<out DbCallable>): StmtParent

        abstract fun expr(e: IrExpression, callable: Label<out DbCallable>): ExprParent
    }

    inner class StmtParent(val parent: Label<out DbStmtparent>, val idx: Int) : StmtExprParent() {
        override fun stmt(e: IrExpression, callable: Label<out DbCallable>) = this

        override fun expr(e: IrExpression, callable: Label<out DbCallable>) =
            extractExpressionStmt(tw.getLocation(e), parent, idx, callable).let { id ->
                ExprParent(id, 0, id)
            }
    }

    inner class ExprParent(
        val parent: Label<out DbExprparent>,
        val idx: Int,
        val enclosingStmt: Label<out DbStmt>
    ) : StmtExprParent() {
        override fun stmt(e: IrExpression, callable: Label<out DbCallable>): StmtParent {
            val id = tw.getFreshIdLabel<DbStmtexpr>()
            val type = useType(e.type)
            val locId = tw.getLocation(e)
            tw.writeExprs_stmtexpr(id, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(id, type.kotlinResult.id)
            extractExprContext(id, locId, callable, enclosingStmt)
            return StmtParent(id, 0)
        }

        override fun expr(e: IrExpression, callable: Label<out DbCallable>): ExprParent {
            return this
        }
    }

    private fun getStatementOriginOperator(origin: IrStatementOrigin?) =
        when (origin) {
            IrStatementOrigin.PLUSEQ -> "plus"
            IrStatementOrigin.MINUSEQ -> "minus"
            IrStatementOrigin.MULTEQ -> "times"
            IrStatementOrigin.DIVEQ -> "div"
            IrStatementOrigin.PERCEQ -> "rem"
            else -> null
        }

    private fun getUpdateInPlaceRHS(
        origin: IrStatementOrigin?,
        isExpectedLhs: (IrExpression?) -> Boolean,
        updateRhs: IrExpression
    ): IrExpression? {
        // Check for a desugared in-place update operator, such as "v += e":
        return getStatementOriginOperator(origin)?.let {
            if (updateRhs is IrCall && isNumericFunction(updateRhs.symbol.owner, it)) {
                // Check for an expression like x = get(x).op(e):
                val opReceiver = updateRhs.dispatchReceiver
                if (isExpectedLhs(opReceiver)) {
                    updateRhs.getValueArgument(0)
                } else null
            } else null
        }
    }

    private fun writeUpdateInPlaceExpr(
        origin: IrStatementOrigin
    ): ((
        tw: TrapWriter,
        id: Label<out DbAssignexpr>,
        type: Label<out DbType>,
        exprParent: Label<out DbExprparent>,
        index: Int
    ) -> Unit)? {
        when (origin) {
            IrStatementOrigin.PLUSEQ ->
                return {
                    tw: TrapWriter,
                    id: Label<out DbAssignexpr>,
                    type: Label<out DbType>,
                    exprParent: Label<out DbExprparent>,
                    index: Int ->
                    tw.writeExprs_assignaddexpr(id.cast<DbAssignaddexpr>(), type, exprParent, index)
                }
            IrStatementOrigin.MINUSEQ ->
                return {
                    tw: TrapWriter,
                    id: Label<out DbAssignexpr>,
                    type: Label<out DbType>,
                    exprParent: Label<out DbExprparent>,
                    index: Int ->
                    tw.writeExprs_assignsubexpr(id.cast<DbAssignsubexpr>(), type, exprParent, index)
                }
            IrStatementOrigin.MULTEQ ->
                return {
                    tw: TrapWriter,
                    id: Label<out DbAssignexpr>,
                    type: Label<out DbType>,
                    exprParent: Label<out DbExprparent>,
                    index: Int ->
                    tw.writeExprs_assignmulexpr(id.cast<DbAssignmulexpr>(), type, exprParent, index)
                }
            IrStatementOrigin.DIVEQ ->
                return {
                    tw: TrapWriter,
                    id: Label<out DbAssignexpr>,
                    type: Label<out DbType>,
                    exprParent: Label<out DbExprparent>,
                    index: Int ->
                    tw.writeExprs_assigndivexpr(id.cast<DbAssigndivexpr>(), type, exprParent, index)
                }
            IrStatementOrigin.PERCEQ ->
                return {
                    tw: TrapWriter,
                    id: Label<out DbAssignexpr>,
                    type: Label<out DbType>,
                    exprParent: Label<out DbExprparent>,
                    index: Int ->
                    tw.writeExprs_assignremexpr(id.cast<DbAssignremexpr>(), type, exprParent, index)
                }
            else -> return null
        }
    }

    /**
     * This method tries to extract a block as an enhanced for loop. It returns true if it succeeds,
     * and false otherwise.
     */
    private fun tryExtractForLoop(
        e: IrContainerExpression,
        callable: Label<out DbCallable>,
        parent: StmtExprParent
    ): Boolean {
        /*
         * We're expecting the pattern
         * {
         *   val iterator = [expr].iterator()
         *   while (iterator.hasNext()) {
         *    val [loopVar] = iterator.next()
         *    [block]
         *   }
         * }
         */

        if (e.origin != IrStatementOrigin.FOR_LOOP || e.statements.size != 2) {
            return false
        }

        val iteratorVariable = e.statements[0] as? IrVariable
        val innerWhile = e.statements[1] as? IrWhileLoop

        if (
            iteratorVariable == null ||
                iteratorVariable.origin != IrDeclarationOrigin.FOR_LOOP_ITERATOR ||
                innerWhile == null ||
                innerWhile.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE
        ) {
            return false
        }

        val initializer = iteratorVariable.initializer as? IrCall
        if (
            initializer == null ||
                initializer.origin != IrStatementOrigin.FOR_LOOP_ITERATOR ||
                initializer.symbol.owner.name.asString() != "iterator"
        ) {
            return false
        }

        val expr = initializer.dispatchReceiver
        val cond = innerWhile.condition as? IrCall
        val body = innerWhile.body as? IrBlock

        if (
            expr == null ||
                cond == null ||
                cond.origin != IrStatementOrigin.FOR_LOOP_HAS_NEXT ||
                (cond.dispatchReceiver as? IrGetValue)?.symbol?.owner != iteratorVariable ||
                body == null ||
                body.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE ||
                body.statements.size < 2
        ) {
            return false
        }

        val loopVar = body.statements[0] as? IrVariable
        val nextCall = loopVar?.initializer as? IrCall

        if (
            loopVar == null ||
                !(loopVar.origin == IrDeclarationOrigin.FOR_LOOP_VARIABLE ||
                    loopVar.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE) ||
                nextCall == null ||
                nextCall.origin != IrStatementOrigin.FOR_LOOP_NEXT ||
                (nextCall.dispatchReceiver as? IrGetValue)?.symbol?.owner != iteratorVariable
        ) {
            return false
        }

        val id =
            extractLoop(innerWhile, null, parent, callable) { p, idx ->
                tw.getFreshIdLabel<DbEnhancedforstmt>().also {
                    tw.writeStmts_enhancedforstmt(it, p, idx, callable)
                }
            }

        extractVariableExpr(loopVar, callable, id, 0, id, extractInitializer = false)
        extractExpressionExpr(expr, callable, id, 1, id)
        val block = body.statements[1] as? IrBlock
        if (body.statements.size == 2 && block != null) {
            // Extract the body that was given to us by the compiler
            extractExpressionStmt(block, callable, id, 2)
        } else {
            // Extract a block with all but the first (loop variable declaration) statement
            extractBlock(body, body.statements.takeLast(body.statements.size - 1), id, 2, callable)
        }

        return true
    }

    /**
     * This tried to extract a block as an array update. It returns true if it succeeds, and false
     * otherwise.
     */
    private fun tryExtractArrayUpdate(
        e: IrContainerExpression,
        callable: Label<out DbCallable>,
        parent: StmtExprParent
    ): Boolean {
        /*
         * We're expecting the pattern
         * {
         *   val array = e1
         *   val idx = e2
         *   array.set(idx, array.get(idx).op(e3))
         * }
         *
         * If we find it, we'll extract e1[e2] op= e3 (op is +, -, ...)
         */
        if (e.statements.size != 3) return false
        (e.statements[0] as? IrVariable)?.let { arrayVarDecl ->
            arrayVarDecl.initializer?.let { arrayVarInitializer ->
                (e.statements[1] as? IrVariable)?.let { indexVarDecl ->
                    indexVarDecl.initializer?.let { indexVarInitializer ->
                        (e.statements[2] as? IrCall)?.let { arraySetCall ->
                            if (
                                isFunction(
                                    arraySetCall.symbol.owner,
                                    "kotlin",
                                    "(some array type)",
                                    { isArrayType(it) },
                                    "set"
                                )
                            ) {
                                val updateRhs0 = arraySetCall.getValueArgument(1)
                                if (updateRhs0 == null) {
                                    logger.errorElement("Update RHS not found", e)
                                    return false
                                }
                                getUpdateInPlaceRHS(
                                        e
                                            .origin, // Using e.origin not arraySetCall.origin here
                                                     // distinguishes a compiler-generated block
                                                     // from a user manually code that looks the
                                                     // same.
                                        { oldValue ->
                                            oldValue is IrCall &&
                                                isFunction(
                                                    oldValue.symbol.owner,
                                                    "kotlin",
                                                    "(some array type)",
                                                    { typeName -> isArrayType(typeName) },
                                                    "get"
                                                ) &&
                                                (oldValue.dispatchReceiver as? IrGetValue)?.let {
                                                    receiverVal ->
                                                    receiverVal.symbol.owner ==
                                                        arrayVarDecl.symbol.owner
                                                } ?: false
                                        },
                                        updateRhs0
                                    )
                                    ?.let { updateRhs ->
                                        val origin = e.origin
                                        if (origin == null) {
                                            logger.errorElement("No origin found", e)
                                            return false
                                        }
                                        val writeUpdateInPlaceExprFun =
                                            writeUpdateInPlaceExpr(origin)
                                        if (writeUpdateInPlaceExprFun == null) {
                                            logger.errorElement("Unexpected origin", e)
                                            return false
                                        }

                                        // Create an assignment skeleton _ op= _
                                        val exprParent = parent.expr(e, callable)
                                        val assignId = tw.getFreshIdLabel<DbAssignexpr>()
                                        val type = useType(arrayVarInitializer.type)
                                        val locId = tw.getLocation(e)
                                        tw.writeExprsKotlinType(assignId, type.kotlinResult.id)
                                        extractExprContext(
                                            assignId,
                                            locId,
                                            callable,
                                            exprParent.enclosingStmt
                                        )

                                        writeUpdateInPlaceExprFun(
                                            tw,
                                            assignId,
                                            type.javaResult.id,
                                            exprParent.parent,
                                            exprParent.idx
                                        )

                                        // Extract e1[e2]
                                        val lhsId = tw.getFreshIdLabel<DbArrayaccess>()
                                        val elementType = useType(updateRhs.type)
                                        tw.writeExprs_arrayaccess(
                                            lhsId,
                                            elementType.javaResult.id,
                                            assignId,
                                            0
                                        )
                                        tw.writeExprsKotlinType(lhsId, elementType.kotlinResult.id)
                                        extractExprContext(
                                            lhsId,
                                            locId,
                                            callable,
                                            exprParent.enclosingStmt
                                        )
                                        extractExpressionExpr(
                                            arrayVarInitializer,
                                            callable,
                                            lhsId,
                                            0,
                                            exprParent.enclosingStmt
                                        )
                                        extractExpressionExpr(
                                            indexVarInitializer,
                                            callable,
                                            lhsId,
                                            1,
                                            exprParent.enclosingStmt
                                        )

                                        // Extract e3
                                        extractExpressionExpr(
                                            updateRhs,
                                            callable,
                                            assignId,
                                            1,
                                            exprParent.enclosingStmt
                                        )

                                        return true
                                    }
                            }
                        }
                    }
                }
            }
        }

        return false
    }

    private fun extractExpressionStmt(
        locId: Label<DbLocation>,
        parent: Label<out DbStmtparent>,
        idx: Int,
        callable: Label<out DbCallable>
    ) =
        tw.getFreshIdLabel<DbExprstmt>().also {
            tw.writeStmts_exprstmt(it, parent, idx, callable)
            tw.writeHasLocation(it, locId)
        }

    private fun extractExpressionStmt(
        e: IrExpression,
        callable: Label<out DbCallable>,
        parent: Label<out DbStmtparent>,
        idx: Int
    ) {
        extractExpression(e, callable, StmtParent(parent, idx))
    }

    fun extractExpressionExpr(
        e: IrExpression,
        callable: Label<out DbCallable>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingStmt: Label<out DbStmt>
    ) {
        extractExpression(e, callable, ExprParent(parent, idx, enclosingStmt))
    }

    private fun extractExprContext(
        id: Label<out DbExpr>,
        locId: Label<DbLocation>,
        callable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?
    ) {
        tw.writeHasLocation(id, locId)
        callable?.let { tw.writeCallableEnclosingExpr(id, it) }
        enclosingStmt?.let { tw.writeStatementEnclosingExpr(id, it) }
    }

    private fun extractEqualsExpression(
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) =
        tw.getFreshIdLabel<DbEqexpr>().also {
            val type = useType(pluginContext.irBuiltIns.booleanType)
            tw.writeExprs_eqexpr(it, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(it, type.kotlinResult.id)
            extractExprContext(it, locId, callable, enclosingStmt)
        }

    private fun extractAndbitExpression(
        type: IrType,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) =
        tw.getFreshIdLabel<DbAndbitexpr>().also {
            val typeResults = useType(type)
            tw.writeExprs_andbitexpr(it, typeResults.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(it, typeResults.kotlinResult.id)
            extractExprContext(it, locId, callable, enclosingStmt)
        }

    private fun extractConstantInteger(
        v: Number,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        overrideId: Label<out DbExpr>? = null
    ) =
        exprIdOrFresh<DbIntegerliteral>(overrideId).also {
            val type = useType(pluginContext.irBuiltIns.intType)
            tw.writeExprs_integerliteral(it, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(it, type.kotlinResult.id)
            tw.writeNamestrings(v.toString(), v.toString(), it)
            extractExprContext(it, locId, callable, enclosingStmt)
        }

    private fun extractNull(
        t: IrType,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        overrideId: Label<out DbExpr>? = null
    ) =
        exprIdOrFresh<DbNullliteral>(overrideId).also {
            val type = useType(t)
            // Match Java by using a special <nulltype> for nulls, rather than Kotlin's view of this which is
            // kotlin.Nothing?, the type that can only contain null.
            val nullTypeName = "<nulltype>"
            val javaNullType = tw.getLabelFor<DbPrimitive>(
                "@\"type;$nullTypeName\"",
                { tw.writePrimitives(it, nullTypeName) }
            )
            tw.writeExprs_nullliteral(it, javaNullType, parent, idx)
            tw.writeExprsKotlinType(it, type.kotlinResult.id)
            extractExprContext(it, locId, callable, enclosingStmt)
        }

    private fun extractAssignExpr(
        type: IrType,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) =
        tw.getFreshIdLabel<DbAssignexpr>().also {
            val typeResults = useType(type)
            tw.writeExprs_assignexpr(it, typeResults.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(it, typeResults.kotlinResult.id)
            extractExprContext(it, locId, callable, enclosingStmt)
        }

    private fun extractExpression(
        e: IrExpression,
        callable: Label<out DbCallable>,
        parent: StmtExprParent
    ) {
        with("expression", e) {
            when (e) {
                is IrDelegatingConstructorCall -> {
                    val stmtParent = parent.stmt(e, callable)

                    val irCallable = declarationStack.peek().first

                    val delegatingClass = e.symbol.owner.parent
                    val currentClass = irCallable.parent

                    if (delegatingClass !is IrClass) {
                        logger.warnElement(
                            "Delegating class isn't a class: " + delegatingClass.javaClass,
                            e
                        )
                    }
                    if (currentClass !is IrClass) {
                        logger.warnElement(
                            "Current class isn't a class: " + currentClass.javaClass,
                            e
                        )
                    }

                    val id: Label<out DbStmt>
                    if (delegatingClass != currentClass) {
                        id = tw.getFreshIdLabel<DbSuperconstructorinvocationstmt>()
                        tw.writeStmts_superconstructorinvocationstmt(
                            id,
                            stmtParent.parent,
                            stmtParent.idx,
                            callable
                        )
                    } else {
                        id = tw.getFreshIdLabel<DbConstructorinvocationstmt>()
                        tw.writeStmts_constructorinvocationstmt(
                            id,
                            stmtParent.parent,
                            stmtParent.idx,
                            callable
                        )
                    }

                    val locId = tw.getLocation(e)
                    val methodId = useFunction<DbConstructor>(e.symbol.owner)
                    if (methodId == null) {
                        logger.errorElement("Cannot get ID for delegating constructor", e)
                    } else {
                        tw.writeCallableBinding(id.cast<DbCaller>(), methodId)
                    }

                    tw.writeHasLocation(id, locId)
                    extractCallValueArguments(id, e, id, callable, 0)
                    val dr = e.dispatchReceiver
                    if (dr != null) {
                        extractExpressionExpr(dr, callable, id, -1, id)
                    }

                    // todo: type arguments at index -2, -3, ...
                }
                is IrThrow -> {
                    val stmtParent = parent.stmt(e, callable)
                    val id = tw.getFreshIdLabel<DbThrowstmt>()
                    val locId = tw.getLocation(e)
                    tw.writeStmts_throwstmt(id, stmtParent.parent, stmtParent.idx, callable)
                    tw.writeHasLocation(id, locId)
                    extractExpressionExpr(e.value, callable, id, 0, id)
                }
                is IrBreak -> {
                    val stmtParent = parent.stmt(e, callable)
                    val id = tw.getFreshIdLabel<DbBreakstmt>()
                    tw.writeStmts_breakstmt(id, stmtParent.parent, stmtParent.idx, callable)
                    extractBreakContinue(e, id)
                }
                is IrContinue -> {
                    val stmtParent = parent.stmt(e, callable)
                    val id = tw.getFreshIdLabel<DbContinuestmt>()
                    tw.writeStmts_continuestmt(id, stmtParent.parent, stmtParent.idx, callable)
                    extractBreakContinue(e, id)
                }
                is IrReturn -> {
                    val stmtParent = parent.stmt(e, callable)
                    val id = tw.getFreshIdLabel<DbReturnstmt>()
                    val locId = tw.getLocation(e)
                    tw.writeStmts_returnstmt(id, stmtParent.parent, stmtParent.idx, callable)
                    tw.writeHasLocation(id, locId)
                    extractExpressionExpr(e.value, callable, id, 0, id)
                }
                is IrTry -> {
                    val stmtParent = parent.stmt(e, callable)
                    val id = tw.getFreshIdLabel<DbTrystmt>()
                    val locId = tw.getLocation(e)
                    tw.writeStmts_trystmt(id, stmtParent.parent, stmtParent.idx, callable)
                    tw.writeHasLocation(id, locId)
                    extractExpressionStmt(e.tryResult, callable, id, -1)
                    val finallyStmt = e.finallyExpression
                    if (finallyStmt != null) {
                        extractExpressionStmt(finallyStmt, callable, id, -2)
                    }
                    for ((catchIdx, catchClause) in e.catches.withIndex()) {
                        val catchId = tw.getFreshIdLabel<DbCatchclause>()
                        tw.writeStmts_catchclause(catchId, id, catchIdx, callable)
                        val catchLocId = tw.getLocation(catchClause)
                        tw.writeHasLocation(catchId, catchLocId)
                        extractTypeAccessRecursive(
                            catchClause.catchParameter.type,
                            tw.getLocation(catchClause.catchParameter),
                            catchId,
                            -1,
                            callable,
                            catchId
                        )
                        extractVariableExpr(
                            catchClause.catchParameter,
                            callable,
                            catchId,
                            0,
                            catchId
                        )
                        extractExpressionStmt(catchClause.result, callable, catchId, 1)
                    }
                }
                is IrContainerExpression -> {
                    if (
                        !tryExtractArrayUpdate(e, callable, parent) &&
                            !tryExtractForLoop(e, callable, parent)
                    ) {

                        extractBlock(e, e.statements, parent, callable)
                    }
                }
                is IrWhileLoop -> {
                    extractLoopWithCondition(e, parent, callable)
                }
                is IrDoWhileLoop -> {
                    extractLoopWithCondition(e, parent, callable)
                }
                is IrInstanceInitializerCall -> {
                    val irConstructor = declarationStack.peek().first as? IrConstructor
                    if (irConstructor == null) {
                        logger.errorElement("IrInstanceInitializerCall outside constructor", e)
                        return
                    }
                    if (needsObinitFunction(irConstructor.parentAsClass)) {
                        val exprParent = parent.expr(e, callable)
                        val id = tw.getFreshIdLabel<DbMethodaccess>()
                        val type = useType(pluginContext.irBuiltIns.unitType)
                        val locId = tw.getLocation(e)
                        val parentClass = irConstructor.parentAsClass
                        val parentId = useDeclarationParentOf(irConstructor, false, null, true)
                        if (parentId == null) {
                            logger.errorElement("Cannot get parent ID for obinit", e)
                            return
                        }
                        val methodLabel = getObinitLabel(parentClass, parentId)
                        val methodId = tw.getLabelFor<DbMethod>(methodLabel)
                        tw.writeExprs_methodaccess(
                            id,
                            type.javaResult.id,
                            exprParent.parent,
                            exprParent.idx
                        )
                        tw.writeExprsKotlinType(id, type.kotlinResult.id)
                        extractExprContext(id, locId, callable, exprParent.enclosingStmt)
                        tw.writeCallableBinding(id, methodId)
                    } else {
                        val stmtParent = parent.stmt(e, callable)
                        extractInstanceInitializerBlock(stmtParent, irConstructor)
                    }
                }
                is IrConstructorCall -> {
                    val exprParent = parent.expr(e, callable)
                    extractConstructorCall(
                        e,
                        exprParent.parent,
                        exprParent.idx,
                        callable,
                        exprParent.enclosingStmt
                    )
                }
                is IrEnumConstructorCall -> {
                    val exprParent = parent.expr(e, callable)
                    extractConstructorCall(
                        e,
                        exprParent.parent,
                        exprParent.idx,
                        callable,
                        exprParent.enclosingStmt
                    )
                }
                is IrCall -> {
                    extractCall(e, callable, parent)
                }
                is IrStringConcatenation -> {
                    val exprParent = parent.expr(e, callable)
                    val id = tw.getFreshIdLabel<DbStringtemplateexpr>()
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_stringtemplateexpr(
                        id,
                        type.javaResult.id,
                        exprParent.parent,
                        exprParent.idx
                    )
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, exprParent.enclosingStmt)
                    e.arguments.forEachIndexed { i, a ->
                        extractExpressionExpr(a, callable, id, i, exprParent.enclosingStmt)
                    }
                }
                is CodeQLIrConst<*> -> {
                    val exprParent = parent.expr(e, callable)
                    extractConstant(
                        e,
                        exprParent.parent,
                        exprParent.idx,
                        callable,
                        exprParent.enclosingStmt
                    )
                }
                is IrGetValue -> {
                    val exprParent = parent.expr(e, callable)
                    val owner = e.symbol.owner
                    if (owner is IrValueParameter && isDispatchReceiver(owner)) {
                        extractThisAccess(e, owner.parent, exprParent, callable)
                    } else {
                        val isAnnotationClassParameter =
                            ((owner as? IrValueParameter)?.parent as? IrConstructor)
                                ?.parentClassOrNull
                                ?.kind == ClassKind.ANNOTATION_CLASS
                        val extractType =
                            if (isAnnotationClassParameter) kClassToJavaClass(e.type) else e.type
                        extractVariableAccess(
                            useValueDeclaration(owner),
                            extractType,
                            tw.getLocation(e),
                            exprParent.parent,
                            exprParent.idx,
                            callable,
                            exprParent.enclosingStmt
                        )
                    }
                }
                is IrGetField -> {
                    val exprParent = parent.expr(e, callable)
                    val owner = tryReplaceAndroidSyntheticField(e.symbol.owner)
                    val locId = tw.getLocation(e)
                    val fieldType =
                        if (isAnnotationClassField(owner)) kClassToJavaClass(e.type) else e.type
                    extractVariableAccess(
                            useField(owner),
                            fieldType,
                            locId,
                            exprParent.parent,
                            exprParent.idx,
                            callable,
                            exprParent.enclosingStmt
                        )
                        .also { id ->
                            val receiver = e.receiver
                            if (receiver != null) {
                                extractExpressionExpr(
                                    receiver,
                                    callable,
                                    id,
                                    -1,
                                    exprParent.enclosingStmt
                                )
                            } else if (owner.isStatic) {
                                extractStaticTypeAccessQualifier(
                                    owner,
                                    id,
                                    locId,
                                    callable,
                                    exprParent.enclosingStmt
                                )
                            }
                        }
                }
                is IrGetEnumValue -> {
                    val exprParent = parent.expr(e, callable)
                    extractEnumValue(
                        e,
                        exprParent.parent,
                        exprParent.idx,
                        callable,
                        exprParent.enclosingStmt
                    )
                }
                is IrSetValue,
                is IrSetField -> {
                    val exprParent = parent.expr(e, callable)
                    val id = tw.getFreshIdLabel<DbAssignexpr>()
                    val type = useType(e.type)
                    val rhsValue =
                        when (e) {
                            is IrSetValue -> e.value
                            is IrSetField -> e.value
                            else -> {
                                logger.errorElement("Unhandled IrSet* element.", e)
                                return
                            }
                        }
                    // The set operation's location as actually that of its LHS. Hence, the
                    // assignment spans the
                    // set op plus its RHS, while the varAccess takes its location from `e`.
                    val locId = tw.getLocation(e.startOffset, rhsValue.endOffset)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, exprParent.enclosingStmt)

                    val lhsId = tw.getFreshIdLabel<DbVaraccess>()
                    val lhsLocId = tw.getLocation(e)
                    extractExprContext(lhsId, lhsLocId, callable, exprParent.enclosingStmt)

                    when (e) {
                        is IrSetValue -> {
                            // Check for a desugared in-place update operator, such as "v += e":
                            val inPlaceUpdateRhs =
                                getUpdateInPlaceRHS(
                                    e.origin,
                                    { it is IrGetValue && it.symbol.owner == e.symbol.owner },
                                    rhsValue
                                )
                            if (inPlaceUpdateRhs != null) {
                                val origin = e.origin
                                if (origin == null) {
                                    logger.errorElement("No origin for set-value", e)
                                    return
                                } else {
                                    val writeUpdateInPlaceExprFun = writeUpdateInPlaceExpr(origin)
                                    if (writeUpdateInPlaceExprFun == null) {
                                        logger.errorElement("Unexpected origin for set-value", e)
                                        return
                                    }
                                    writeUpdateInPlaceExprFun(
                                        tw,
                                        id,
                                        type.javaResult.id,
                                        exprParent.parent,
                                        exprParent.idx
                                    )
                                }
                            } else {
                                tw.writeExprs_assignexpr(
                                    id,
                                    type.javaResult.id,
                                    exprParent.parent,
                                    exprParent.idx
                                )
                            }

                            val lhsType = useType(e.symbol.owner.type)
                            tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, id, 0)
                            tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id)
                            val vId = useValueDeclaration(e.symbol.owner)
                            if (vId != null) {
                                tw.writeVariableBinding(lhsId, vId)
                            }
                            extractExpressionExpr(
                                inPlaceUpdateRhs ?: rhsValue,
                                callable,
                                id,
                                1,
                                exprParent.enclosingStmt
                            )
                        }
                        is IrSetField -> {
                            tw.writeExprs_assignexpr(
                                id,
                                type.javaResult.id,
                                exprParent.parent,
                                exprParent.idx
                            )
                            val realField = tryReplaceAndroidSyntheticField(e.symbol.owner)
                            val lhsType = useType(realField.type)
                            tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, id, 0)
                            tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id)
                            val vId = useField(realField)
                            tw.writeVariableBinding(lhsId, vId)
                            extractExpressionExpr(
                                e.value,
                                callable,
                                id,
                                1,
                                exprParent.enclosingStmt
                            )

                            val receiver = e.receiver
                            if (receiver != null) {
                                extractExpressionExpr(
                                    receiver,
                                    callable,
                                    lhsId,
                                    -1,
                                    exprParent.enclosingStmt
                                )
                            } else if (realField.isStatic) {
                                extractStaticTypeAccessQualifier(
                                    realField,
                                    lhsId,
                                    lhsLocId,
                                    callable,
                                    exprParent.enclosingStmt
                                )
                            }
                        }
                        else -> {
                            logger.errorElement("Unhandled IrSet* element.", e)
                        }
                    }
                }
                is IrWhen -> {
                    val isAndAnd = e.origin == IrStatementOrigin.ANDAND
                    val isOrOr = e.origin == IrStatementOrigin.OROR

                    if (
                        (isAndAnd || isOrOr) &&
                            e.branches.size == 2 &&
                            (e.branches[1].condition as? CodeQLIrConst<*>)?.value == true &&
                            (e.branches[if (e.origin == IrStatementOrigin.ANDAND) 1 else 0].result
                                    as? CodeQLIrConst<*>)
                                ?.value == isOrOr
                    ) {

                        // resugar binary logical operators:

                        val exprParent = parent.expr(e, callable)
                        val type = useType(e.type)

                        val id =
                            if (e.origin == IrStatementOrigin.ANDAND) {
                                val id = tw.getFreshIdLabel<DbAndlogicalexpr>()
                                tw.writeExprs_andlogicalexpr(
                                    id,
                                    type.javaResult.id,
                                    exprParent.parent,
                                    exprParent.idx
                                )
                                id
                            } else {
                                val id = tw.getFreshIdLabel<DbOrlogicalexpr>()
                                tw.writeExprs_orlogicalexpr(
                                    id,
                                    type.javaResult.id,
                                    exprParent.parent,
                                    exprParent.idx
                                )
                                id
                            }
                        val locId = tw.getLocation(e)

                        tw.writeExprsKotlinType(id, type.kotlinResult.id)
                        extractExprContext(id, locId, callable, exprParent.enclosingStmt)

                        extractExpressionExpr(
                            e.branches[0].condition,
                            callable,
                            id,
                            0,
                            exprParent.enclosingStmt
                        )

                        var rhsIdx = if (e.origin == IrStatementOrigin.ANDAND) 0 else 1
                        extractExpressionExpr(
                            e.branches[rhsIdx].result,
                            callable,
                            id,
                            1,
                            exprParent.enclosingStmt
                        )

                        return
                    }

                    val exprParent = parent.expr(e, callable)
                    val id = tw.getFreshIdLabel<DbWhenexpr>()
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_whenexpr(
                        id,
                        type.javaResult.id,
                        exprParent.parent,
                        exprParent.idx
                    )
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, exprParent.enclosingStmt)
                    if (e.origin == IrStatementOrigin.IF) {
                        tw.writeWhen_if(id)
                    }
                    e.branches.forEachIndexed { i, b ->
                        val bId = tw.getFreshIdLabel<DbWhenbranch>()
                        val bLocId = tw.getLocation(b)
                        tw.writeStmts_whenbranch(bId, id, i, callable)
                        tw.writeHasLocation(bId, bLocId)
                        extractExpressionExpr(b.condition, callable, bId, 0, bId)
                        extractExpressionStmt(b.result, callable, bId, 1)
                        if (b is IrElseBranch) {
                            tw.writeWhen_branch_else(bId)
                        }
                    }
                }
                is IrGetClass -> {
                    val exprParent = parent.expr(e, callable)
                    val id = tw.getFreshIdLabel<DbGetclassexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_getclassexpr(
                        id,
                        type.javaResult.id,
                        exprParent.parent,
                        exprParent.idx
                    )
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, exprParent.enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 0, exprParent.enclosingStmt)
                }
                is IrTypeOperatorCall -> {
                    val exprParent = parent.expr(e, callable)
                    extractTypeOperatorCall(
                        e,
                        callable,
                        exprParent.parent,
                        exprParent.idx,
                        exprParent.enclosingStmt
                    )
                }
                is IrVararg -> {
                    // There are lowered IR cases when the vararg expression is not within a call,
                    // such as
                    // val temp0 = [*expr].
                    // This AST element can also occur as a collection literal in an annotation
                    // class, such as
                    // annotation class Ann(val strings: Array<String> = [])
                    val exprParent = parent.expr(e, callable)
                    extractArrayCreation(
                        e,
                        e.type,
                        e.varargElementType,
                        true,
                        e,
                        exprParent.parent,
                        exprParent.idx,
                        callable,
                        exprParent.enclosingStmt
                    )
                }
                is IrGetObjectValue -> {
                    // For `object MyObject { ... }`, the .class has an
                    // automatically-generated `public static final MyObject INSTANCE`
                    // field that we are accessing here.
                    val exprParent = parent.expr(e, callable)
                    val c = getBoundSymbolOwner(e.symbol, e) ?: return

                    val instance =
                        if (c.isCompanion) useCompanionObjectClassInstance(c)
                        else useObjectClassInstance(c)

                    if (instance != null) {
                        val id = tw.getFreshIdLabel<DbVaraccess>()
                        val type = useType(e.type)
                        val locId = tw.getLocation(e)
                        tw.writeExprs_varaccess(
                            id,
                            type.javaResult.id,
                            exprParent.parent,
                            exprParent.idx
                        )
                        tw.writeExprsKotlinType(id, type.kotlinResult.id)
                        extractExprContext(id, locId, callable, exprParent.enclosingStmt)

                        tw.writeVariableBinding(id, instance.id)
                    }
                }
                is IrFunctionReference -> {
                    extractFunctionReference(e, parent, callable)
                }
                is IrFunctionExpression -> {
                    /*
                     * Extract generated class:
                     * ```
                     * class C : Any, kotlin.FunctionI<T0,T1, ... TI, R> {
                     *   constructor() { super(); }
                     *   fun invoke(a0:T0, a1:T1, ... aI: TI): R { ... }
                     * }
                     * ```
                     * or in case of big arity lambdas
                     * ```
                     * class C : Any, kotlin.FunctionN<R> {
                     *   constructor() { super(); }
                     *   fun invoke(a0:T0, a1:T1, ... aI: TI): R { ... }
                     *   fun invoke(vararg args: Any?): R {
                     *     return invoke(args[0] as T0, args[1] as T1, ..., args[I] as TI)
                     *   }
                     * }
                     * ```
                     **/

                    val ids = getLocallyVisibleFunctionLabels(e.function)
                    val locId = tw.getLocation(e)

                    val ext = e.function.extensionReceiverParameter
                    val parameters =
                        if (ext != null) {
                            listOf(ext) + e.function.valueParameters
                        } else {
                            e.function.valueParameters
                        }

                    var types = parameters.map { it.type }
                    types += e.function.returnType

                    val isBigArity = types.size > BuiltInFunctionArity.BIG_ARITY
                    if (isBigArity) {
                        implementFunctionNInvoke(e.function, ids, locId, parameters)
                    } else {
                        addModifiers(ids.function, "override")
                    }

                    val exprParent = parent.expr(e, callable)
                    val idLambdaExpr = tw.getFreshIdLabel<DbLambdaexpr>()
                    tw.writeExprs_lambdaexpr(
                        idLambdaExpr,
                        ids.type.javaResult.id,
                        exprParent.parent,
                        exprParent.idx
                    )
                    tw.writeExprsKotlinType(idLambdaExpr, ids.type.kotlinResult.id)
                    extractExprContext(idLambdaExpr, locId, callable, exprParent.enclosingStmt)
                    tw.writeCallableBinding(idLambdaExpr, ids.constructor)

                    // todo: fix hard coded block body of lambda
                    tw.writeLambdaKind(idLambdaExpr, 1)

                    val fnInterfaceType = getFunctionalInterfaceType(types)
                    if (fnInterfaceType == null) {
                        logger.warnElement(
                            "Cannot find functional interface type for function expression",
                            e
                        )
                    } else {
                        val id =
                            extractGeneratedClass(
                                e
                                    .function, // We're adding this function as a member, and
                                               // changing its name to `invoke` to implement
                                               // `kotlin.FunctionX<,,,>.invoke(,,)`
                                listOf(pluginContext.irBuiltIns.anyType, fnInterfaceType)
                            )

                        extractTypeAccessRecursive(
                            fnInterfaceType,
                            locId,
                            idLambdaExpr,
                            -3,
                            callable,
                            exprParent.enclosingStmt
                        )

                        tw.writeIsAnonymClass(id, idLambdaExpr)
                    }
                }
                is IrClassReference -> {
                    val exprParent = parent.expr(e, callable)
                    extractClassReference(
                        e,
                        exprParent.parent,
                        exprParent.idx,
                        callable,
                        exprParent.enclosingStmt
                    )
                }
                is IrPropertyReference -> {
                    extractPropertyReference(
                        "property reference",
                        e,
                        e.getter,
                        e.setter,
                        e.field,
                        parent,
                        callable
                    )
                }
                is IrLocalDelegatedPropertyReference -> {
                    extractPropertyReference(
                        "local delegated property reference",
                        e,
                        e.getter,
                        e.setter,
                        null,
                        parent,
                        callable
                    )
                }
                else -> {
                    logger.errorElement("Unrecognised IrExpression: " + e.javaClass, e)
                }
            }
            return
        }
    }

    private fun extractBlock(
        e: IrContainerExpression,
        statements: List<IrStatement>,
        parent: StmtExprParent,
        callable: Label<out DbCallable>
    ) {
        val stmtParent = parent.stmt(e, callable)
        extractBlock(e, statements, stmtParent.parent, stmtParent.idx, callable)
    }

    private fun extractBlock(
        e: IrElement,
        statements: List<IrStatement>,
        parent: Label<out DbStmtparent>,
        idx: Int,
        callable: Label<out DbCallable>
    ) {
        val id = tw.getFreshIdLabel<DbBlock>()
        val locId = tw.getLocation(e)
        tw.writeStmts_block(id, parent, idx, callable)
        tw.writeHasLocation(id, locId)
        statements.forEachIndexed { i, s -> extractStatement(s, callable, id, i) }
    }

    private inline fun <D : DeclarationDescriptor, reified B : IrSymbolOwner> getBoundSymbolOwner(
        symbol: IrBindableSymbol<D, B>,
        e: IrExpression
    ): B? {
        if (symbol.isBound) {
            return symbol.owner
        }

        logger.errorElement("Unbound symbol found, skipping extraction of expression", e)
        return null
    }

    private fun extractSuperAccess(
        irType: IrType,
        callable: Label<out DbCallable>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingStmt: Label<out DbStmt>,
        locId: Label<DbLocation>
    ) =
        tw.getFreshIdLabel<DbSuperaccess>().also {
            val type = useType(irType)
            tw.writeExprs_superaccess(it, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(it, type.kotlinResult.id)
            extractExprContext(it, locId, callable, enclosingStmt)
            extractTypeAccessRecursive(irType, locId, it, 0)
        }

    private fun extractThisAccess(
        type: TypeResults,
        callable: Label<out DbCallable>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingStmt: Label<out DbStmt>,
        locId: Label<DbLocation>
    ) =
        tw.getFreshIdLabel<DbThisaccess>().also {
            tw.writeExprs_thisaccess(it, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(it, type.kotlinResult.id)
            extractExprContext(it, locId, callable, enclosingStmt)
        }

    private fun extractThisAccess(
        irType: IrType,
        callable: Label<out DbCallable>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingStmt: Label<out DbStmt>,
        locId: Label<DbLocation>
    ) = extractThisAccess(useType(irType), callable, parent, idx, enclosingStmt, locId)

    private fun extractThisAccess(
        e: IrGetValue,
        thisParamParent: IrDeclarationParent,
        exprParent: ExprParent,
        callable: Label<out DbCallable>
    ) {
        val containingDeclaration = declarationStack.peek().first
        val locId = tw.getLocation(e)

        if (
            containingDeclaration.shouldExtractAsStatic &&
                containingDeclaration.parentClassOrNull?.isNonCompanionObject == true
        ) {
            // Use of `this` in a non-companion object member that will be lowered to a static
            // function -- replace with a reference
            // to the corresponding static object instance.
            val instanceField = useObjectClassInstance(containingDeclaration.parentAsClass)
            extractVariableAccess(
                    instanceField.id,
                    e.type,
                    locId,
                    exprParent.parent,
                    exprParent.idx,
                    callable,
                    exprParent.enclosingStmt
                )
                .also { varAccessId ->
                    extractStaticTypeAccessQualifier(
                        containingDeclaration,
                        varAccessId,
                        locId,
                        callable,
                        exprParent.enclosingStmt
                    )
                }
        } else {
            if (thisParamParent is IrFunction) {
                val overriddenAttributes =
                    declarationStack.findOverriddenAttributes(thisParamParent)
                val replaceWithParamIdx =
                    overriddenAttributes?.valueParameters?.indexOf(e.symbol.owner)
                if (replaceWithParamIdx != null && replaceWithParamIdx != -1) {
                    // Use of 'this' in a function where the dispatch receiver is passed like an
                    // ordinary parameter,
                    // such as a `$default` static function that substitutes in default arguments as
                    // needed.
                    val paramDeclarerId =
                        overriddenAttributes.id ?: useDeclarationParent(thisParamParent, false)
                    val replacementParamId =
                        tw.getLabelFor<DbParam>(
                            getValueParameterLabel(paramDeclarerId, replaceWithParamIdx)
                        )
                    extractVariableAccess(
                        replacementParamId,
                        e.type,
                        locId,
                        exprParent.parent,
                        exprParent.idx,
                        callable,
                        exprParent.enclosingStmt
                    )
                    return
                }
            }

            val id =
                extractThisAccess(
                    e.type,
                    callable,
                    exprParent.parent,
                    exprParent.idx,
                    exprParent.enclosingStmt,
                    locId
                )

            fun extractTypeAccess(parent: IrClass) {
                extractTypeAccessRecursive(
                    parent.typeWith(listOf()),
                    locId,
                    id,
                    0,
                    callable,
                    exprParent.enclosingStmt
                )
            }

            val owner = e.symbol.owner
            when (val ownerParent = owner.parent) {
                is IrFunction -> {
                    if (
                        ownerParent.dispatchReceiverParameter == owner &&
                            ownerParent.extensionReceiverParameter != null
                    ) {

                        val ownerParent2 = ownerParent.parent
                        if (ownerParent2 is IrClass) {
                            extractTypeAccess(ownerParent2)
                        } else {
                            logger.errorElement("Unhandled qualifier for this", e)
                        }
                    }
                }
                is IrClass -> {
                    if (ownerParent.thisReceiver == owner) {
                        extractTypeAccess(ownerParent)
                    }
                }
                else -> {
                    logger.errorElement(
                        "Unexpected owner parent for this access: " + ownerParent.javaClass,
                        e
                    )
                }
            }
        }
    }

    private fun extractVariableAccess(
        variable: Label<out DbVariable>?,
        type: TypeResults,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) =
        tw.getFreshIdLabel<DbVaraccess>().also {
            tw.writeExprs_varaccess(it, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(it, type.kotlinResult.id)
            extractExprContext(it, locId, callable, enclosingStmt)

            if (variable != null) {
                tw.writeVariableBinding(it, variable)
            }
        }

    private fun extractVariableAccess(
        variable: Label<out DbVariable>?,
        irType: IrType,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        callable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) =
        extractVariableAccess(
            variable,
            useType(irType),
            locId,
            parent,
            idx,
            callable,
            enclosingStmt
        )

    private fun extractLoop(
        loop: IrLoop,
        bodyIdx: Int?,
        stmtExprParent: StmtExprParent,
        callable: Label<out DbCallable>,
        getId: (Label<out DbStmtparent>, Int) -> Label<out DbStmt>
    ): Label<out DbStmt> {
        val stmtParent = stmtExprParent.stmt(loop, callable)
        val locId = tw.getLocation(loop)

        val idx: Int
        val parent: Label<out DbStmtparent>

        val label = loop.label
        if (label != null) {
            val labeledStmt = tw.getFreshIdLabel<DbLabeledstmt>()
            tw.writeStmts_labeledstmt(labeledStmt, stmtParent.parent, stmtParent.idx, callable)
            tw.writeHasLocation(labeledStmt, locId)

            tw.writeNamestrings(label, "", labeledStmt)
            idx = 0
            parent = labeledStmt
        } else {
            idx = stmtParent.idx
            parent = stmtParent.parent
        }

        val id = getId(parent, idx)
        tw.writeHasLocation(id, locId)

        val body = loop.body
        if (body != null && bodyIdx != null) {
            extractExpressionStmt(body, callable, id, bodyIdx)
        }

        return id
    }

    private fun extractLoopWithCondition(
        loop: IrLoop,
        stmtExprParent: StmtExprParent,
        callable: Label<out DbCallable>
    ) {
        val id =
            extractLoop(loop, 1, stmtExprParent, callable) { parent, idx ->
                if (loop is IrWhileLoop) {
                    tw.getFreshIdLabel<DbWhilestmt>().also {
                        tw.writeStmts_whilestmt(it, parent, idx, callable)
                    }
                } else {
                    tw.getFreshIdLabel<DbDostmt>().also {
                        tw.writeStmts_dostmt(it, parent, idx, callable)
                    }
                }
            }
        extractExpressionExpr(loop.condition, callable, id, 0, id)
    }

    private fun <T : DbExpr> exprIdOrFresh(id: Label<out DbExpr>?) =
        id?.cast<T>() ?: tw.getFreshIdLabel()

    private fun extractClassReference(
        e: IrClassReference,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        overrideId: Label<out DbExpr>? = null,
        typeAccessOverrideId: Label<out DbExpr>? = null,
        useJavaLangClassType: Boolean = false
    ) =
        exprIdOrFresh<DbTypeliteral>(overrideId).also { id ->
            val locId = tw.getLocation(e)
            val jlcType =
                if (useJavaLangClassType) this.javaLangClass?.let { it.typeWith() } else null
            val type = useType(jlcType ?: e.type)
            tw.writeExprs_typeliteral(id, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(id, type.kotlinResult.id)
            extractExprContext(id, locId, enclosingCallable, enclosingStmt)

            extractTypeAccessRecursive(
                e.classType,
                locId,
                id,
                0,
                enclosingCallable,
                enclosingStmt,
                overrideId = typeAccessOverrideId
            )
        }

    private fun extractEnumValue(
        e: IrGetEnumValue,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        extractTypeAccess: Boolean = true,
        overrideId: Label<out DbExpr>? = null
    ) =
        exprIdOrFresh<DbVaraccess>(overrideId).also { id ->
            val type = useType(e.type)
            val locId = tw.getLocation(e)
            tw.writeExprs_varaccess(id, type.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(id, type.kotlinResult.id)
            extractExprContext(id, locId, enclosingCallable, enclosingStmt)

            getBoundSymbolOwner(e.symbol, e)?.let { owner ->
                val vId = useEnumEntry(owner)
                tw.writeVariableBinding(id, vId)

                if (extractTypeAccess)
                    extractStaticTypeAccessQualifier(
                        owner,
                        id,
                        locId,
                        enclosingCallable,
                        enclosingStmt
                    )
            }
        }

    private fun escapeCharForQuotedLiteral(c: Char) =
        when (c) {
            '\r' -> "\\r"
            '\n' -> "\\n"
            '\t' -> "\\t"
            '\\' -> "\\\\"
            '"' -> "\\\""
            else -> c.toString()
        }

    // Render a string literal as it might occur in Kotlin source. Note this is a reasonable guess;
    // the real source
    // could use other escape sequences to describe the same String. Importantly, this is the same
    // guess the Java
    // extractor makes regarding string literals occurring within annotations, which we need to
    // coincide with to ensure
    // database consistency.
    private fun toQuotedLiteral(s: String) =
        s.toCharArray().joinToString(separator = "", prefix = "\"", postfix = "\"") { c ->
            escapeCharForQuotedLiteral(c)
        }

    private fun extractConstant(
        e: CodeQLIrConst<*>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        overrideId: Label<out DbExpr>? = null
    ): Label<out DbExpr>? {

        val v = e.value
        return when {
            v is Number && (v is Int || v is Short || v is Byte) -> {
                extractConstantInteger(
                    v,
                    tw.getLocation(e),
                    parent,
                    idx,
                    enclosingCallable,
                    enclosingStmt,
                    overrideId = overrideId
                )
            }
            v is Long -> {
                exprIdOrFresh<DbLongliteral>(overrideId).also { id ->
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_longliteral(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, enclosingCallable, enclosingStmt)
                    tw.writeNamestrings(v.toString(), v.toString(), id)
                }
            }
            v is Float -> {
                exprIdOrFresh<DbFloatingpointliteral>(overrideId).also { id ->
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_floatingpointliteral(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, enclosingCallable, enclosingStmt)
                    tw.writeNamestrings(v.toString(), v.toString(), id)
                }
            }
            v is Double -> {
                exprIdOrFresh<DbDoubleliteral>(overrideId).also { id ->
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_doubleliteral(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, enclosingCallable, enclosingStmt)
                    tw.writeNamestrings(v.toString(), v.toString(), id)
                }
            }
            v is Boolean -> {
                exprIdOrFresh<DbBooleanliteral>(overrideId).also { id ->
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_booleanliteral(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, enclosingCallable, enclosingStmt)
                    tw.writeNamestrings(v.toString(), v.toString(), id)
                }
            }
            v is Char -> {
                exprIdOrFresh<DbCharacterliteral>(overrideId).also { id ->
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_characterliteral(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, enclosingCallable, enclosingStmt)
                    tw.writeNamestrings(v.toString(), v.toString(), id)
                }
            }
            v is String -> {
                exprIdOrFresh<DbStringliteral>(overrideId).also { id ->
                    val type = useType(e.type)
                    val locId = tw.getLocation(e)
                    tw.writeExprs_stringliteral(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, enclosingCallable, enclosingStmt)
                    tw.writeNamestrings(toQuotedLiteral(v.toString()), v.toString(), id)
                }
            }
            v == null -> {
                extractNull(
                    e.type,
                    tw.getLocation(e),
                    parent,
                    idx,
                    enclosingCallable,
                    enclosingStmt,
                    overrideId = overrideId
                )
            }
            else -> {
                null.also { logger.errorElement("Unrecognised IrConst: " + v.javaClass, e) }
            }
        }
    }

    private open inner class GeneratedClassHelper(
        protected val locId: Label<DbLocation>,
        protected val ids: GeneratedClassLabels
    ) {
        protected val classId = ids.type.javaResult.id.cast<DbClassorinterface>()

        /**
         * Extract a parameter to field assignment, such as `this.field = paramName` below:
         * ```
         * constructor(paramName: type) {
         *   this.field = paramName
         * }
         * ```
         */
        fun extractParameterToFieldAssignmentInConstructor(
            paramName: String,
            paramType: IrType,
            fieldId: Label<DbField>,
            paramIdx: Int,
            stmtIdx: Int
        ) {
            val paramId = tw.getFreshIdLabel<DbParam>()
            extractValueParameter(
                paramId,
                paramType,
                paramName,
                locId,
                ids.constructor,
                paramIdx,
                paramId,
                syntheticParameterNames = false,
                isVararg = false,
                isNoinline = false,
                isCrossinline = false
            )

            extractExpressionStmt(locId, ids.constructorBlock, stmtIdx, ids.constructor).also {
                assignmentStmtId ->
                extractAssignExpr(
                        paramType,
                        locId,
                        assignmentStmtId,
                        0,
                        ids.constructor,
                        assignmentStmtId
                    )
                    .also { assignmentId ->
                        extractVariableAccess(
                                fieldId,
                                paramType,
                                locId,
                                assignmentId,
                                0,
                                ids.constructor,
                                assignmentStmtId
                            )
                            .also { lhsId ->
                                extractThisAccess(
                                    ids.type,
                                    ids.constructor,
                                    lhsId,
                                    -1,
                                    assignmentStmtId,
                                    locId
                                )
                            }
                        extractVariableAccess(
                            paramId,
                            paramType,
                            locId,
                            assignmentId,
                            1,
                            ids.constructor,
                            assignmentStmtId
                        )
                    }
            }
        }
    }

    data class ReceiverInfo(
        val receiver: IrExpression,
        val type: IrType,
        val field: Label<DbField>,
        val indexOffset: Int
    )

    private fun makeReceiverInfo(receiver: IrExpression?, indexOffset: Int): ReceiverInfo? {
        if (receiver == null) {
            return null
        }
        val type = receiver.type
        val field: Label<DbField> = tw.getFreshIdLabel()
        return ReceiverInfo(receiver, type, field, indexOffset)
    }

    /**
     * This is used when extracting callable references, i.e. `::someCallable` or
     * `::someReceiver::someCallable`.
     */
    private open inner class CallableReferenceHelper(
        protected val callableReferenceExpr: IrCallableReference<out IrSymbol>,
        locId: Label<DbLocation>,
        ids: GeneratedClassLabels
    ) : GeneratedClassHelper(locId, ids) {

        // Only one of the receivers can be non-null, but we defensively handle the case when both
        // are null anyway
        private val dispatchReceiverInfo =
            makeReceiverInfo(callableReferenceExpr.dispatchReceiver, 0)
        private val extensionReceiverInfo =
            makeReceiverInfo(
                callableReferenceExpr.extensionReceiver,
                if (dispatchReceiverInfo == null) 0 else 1
            )

        fun extractReceiverField() {
            val firstAssignmentStmtIdx = 1

            if (dispatchReceiverInfo != null) {
                extractField(
                    dispatchReceiverInfo.field,
                    "<dispatchReceiver>",
                    dispatchReceiverInfo.type,
                    classId,
                    locId,
                    DescriptorVisibilities.PRIVATE,
                    callableReferenceExpr,
                    isExternalDeclaration = false,
                    isFinal = true,
                    isStatic = false
                )
                extractParameterToFieldAssignmentInConstructor(
                    "<dispatchReceiver>",
                    dispatchReceiverInfo.type,
                    dispatchReceiverInfo.field,
                    0 + dispatchReceiverInfo.indexOffset,
                    firstAssignmentStmtIdx + dispatchReceiverInfo.indexOffset
                )
            }

            if (extensionReceiverInfo != null) {
                extractField(
                    extensionReceiverInfo.field,
                    "<extensionReceiver>",
                    extensionReceiverInfo.type,
                    classId,
                    locId,
                    DescriptorVisibilities.PRIVATE,
                    callableReferenceExpr,
                    isExternalDeclaration = false,
                    isFinal = true,
                    isStatic = false
                )
                extractParameterToFieldAssignmentInConstructor(
                    "<extensionReceiver>",
                    extensionReceiverInfo.type,
                    extensionReceiverInfo.field,
                    0 + extensionReceiverInfo.indexOffset,
                    firstAssignmentStmtIdx + extensionReceiverInfo.indexOffset
                )
            }
        }

        protected fun writeVariableAccessInFunctionBody(
            pType: TypeResults,
            idx: Int,
            variable: Label<out DbVariable>,
            parent: Label<out DbExprparent>,
            callable: Label<out DbCallable>,
            stmt: Label<out DbStmt>
        ): Label<DbVaraccess> {
            val pId = tw.getFreshIdLabel<DbVaraccess>()
            tw.writeExprs_varaccess(pId, pType.javaResult.id, parent, idx)
            tw.writeExprsKotlinType(pId, pType.kotlinResult.id)
            tw.writeVariableBinding(pId, variable)
            extractExprContext(pId, locId, callable, stmt)
            return pId
        }

        private fun writeFieldAccessInFunctionBody(
            pType: IrType,
            idx: Int,
            variable: Label<out DbField>,
            parent: Label<out DbExprparent>,
            callable: Label<out DbCallable>,
            stmt: Label<out DbStmt>
        ) {
            val accessId =
                writeVariableAccessInFunctionBody(
                    useType(pType),
                    idx,
                    variable,
                    parent,
                    callable,
                    stmt
                )
            writeThisAccess(accessId, callable, stmt)
        }

        protected fun writeThisAccess(
            parent: Label<out DbExprparent>,
            callable: Label<out DbCallable>,
            stmt: Label<out DbStmt>
        ) {
            extractThisAccess(ids.type, callable, parent, -1, stmt, locId)
        }

        fun extractFieldWriteOfReflectionTarget(
            labels: FunctionLabels, // labels of the containing function
            target: IrFieldSymbol, // the target field being accessed)
        ) {
            val fieldType = useType(target.owner.type)

            extractExpressionStmt(locId, labels.blockId, 0, labels.methodId).also { exprStmtId ->
                extractAssignExpr(
                        target.owner.type,
                        locId,
                        exprStmtId,
                        0,
                        labels.methodId,
                        exprStmtId
                    )
                    .also { assignExprId ->
                        extractFieldAccess(fieldType, assignExprId, exprStmtId, labels, target)
                        val p = labels.parameters.first()
                        writeVariableAccessInFunctionBody(
                            p.second,
                            1,
                            p.first,
                            assignExprId,
                            labels.methodId,
                            exprStmtId
                        )
                    }
            }
        }

        fun extractFieldReturnOfReflectionTarget(
            labels: FunctionLabels, // labels of the containing function
            target: IrFieldSymbol, // the target field being accessed
        ) {
            val retId = tw.getFreshIdLabel<DbReturnstmt>()
            tw.writeStmts_returnstmt(retId, labels.blockId, 0, labels.methodId)
            tw.writeHasLocation(retId, locId)

            val fieldType = useType(target.owner.type)
            extractFieldAccess(fieldType, retId, retId, labels, target)
        }

        private fun extractFieldAccess(
            fieldType: TypeResults,
            parent: Label<out DbExprparent>,
            stmt: Label<out DbStmt>,
            labels: FunctionLabels,
            target: IrFieldSymbol
        ) {
            val accessId = tw.getFreshIdLabel<DbVaraccess>()
            tw.writeExprs_varaccess(accessId, fieldType.javaResult.id, parent, 0)
            tw.writeExprsKotlinType(accessId, fieldType.kotlinResult.id)

            extractExprContext(accessId, locId, labels.methodId, stmt)

            val fieldId = useField(target.owner)
            tw.writeVariableBinding(accessId, fieldId)

            if (dispatchReceiverInfo != null) {
                writeFieldAccessInFunctionBody(
                    dispatchReceiverInfo.type,
                    -1,
                    dispatchReceiverInfo.field,
                    accessId,
                    labels.methodId,
                    stmt
                )
            }
        }

        /**
         * Extracts a call to `target` inside the function identified by `labels`. Special
         * parameters (`dispatch` and `extension`) are also handled.
         *
         * Examples are:
         * ```
         * this.<dispatchReceiver>.fn(this.<extensionReceiver>, param1, param2, param3, ...)
         * param1.fn(this.<extensionReceiver>, param2, ...)
         * param1.fn(param2, param3, ...)
         * fn(this.<extensionReceiver>, param1, param2, ...)
         * fn(param1, param2, ...)
         * new MyType(param1, param2, ...)
         * ```
         *
         * The parameters with default argument values cover special cases:
         * - dispatchReceiverIdx is usually -1, except if a constructor is referenced
         * - big arity function references need to call `invoke` with arguments received in an
         *   object array: `fn(param1[0] as T0, param1[1] as T1, ...)`
         */
        fun extractCallToReflectionTarget(
            labels: FunctionLabels, // labels of the containing function
            target: IrFunctionSymbol, // the target function/constructor being called
            returnType:
                IrType, // the return type of the called function. Note that
                        // `target.owner.returnType` and `returnType` doesn't match for generic
                        // functions
            expressionTypeArgs: List<IrType>, // type arguments of the extracted expression
            classTypeArgsIncludingOuterClasses:
                List<
                    IrTypeArgument
                >?, // type arguments of the class containing the callable reference
            dispatchReceiverIdx: Int =
                -1, // dispatch receiver index: -1 in case of functions, -2 for constructors
            bigArityParameterTypes: List<IrType>? =
                null // parameter types used for the cast expressions in a big arity `invoke`
                     // invocation. null if not a big arity invocation.
        ) {
            // Return statement of generated function:
            val retId = tw.getFreshIdLabel<DbReturnstmt>()
            tw.writeStmts_returnstmt(retId, labels.blockId, 0, labels.methodId)
            tw.writeHasLocation(retId, locId)

            // Call to target function:
            val callType = useType(returnType)

            val callId: Label<out DbExpr> =
                if (target is IrConstructorSymbol) {
                    val callId = tw.getFreshIdLabel<DbNewexpr>()
                    tw.writeExprs_newexpr(callId, callType.javaResult.id, retId, 0)
                    tw.writeExprsKotlinType(callId, callType.kotlinResult.id)

                    extractConstructorTypeAccess(
                        returnType,
                        callType,
                        target,
                        locId,
                        callId,
                        -3,
                        labels.methodId,
                        retId
                    )
                    callId
                } else {
                    val callId = tw.getFreshIdLabel<DbMethodaccess>()
                    tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0)
                    tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
                    extractTypeArguments(
                        expressionTypeArgs,
                        locId,
                        callId,
                        labels.methodId,
                        retId,
                        -2,
                        true
                    )
                    callId
                }

            extractExprContext(callId, locId, labels.methodId, retId)

            val callableId =
                useFunction<DbCallable>(
                    target.owner.realOverrideTarget,
                    classTypeArgsIncludingOuterClasses
                )
            if (callableId == null) {
                logger.error("Cannot get ID for reflection target")
            } else {
                tw.writeCallableBinding(callId.cast<DbCaller>(), callableId)
            }

            val useFirstArgAsDispatch: Boolean
            if (dispatchReceiverInfo != null) {
                writeFieldAccessInFunctionBody(
                    dispatchReceiverInfo.type,
                    dispatchReceiverIdx,
                    dispatchReceiverInfo.field,
                    callId,
                    labels.methodId,
                    retId
                )

                useFirstArgAsDispatch = false
            } else {
                if (target.owner.isLocalFunction()) {
                    val ids = getLocallyVisibleFunctionLabels(target.owner)
                    extractNewExprForLocalFunction(ids, callId, locId, labels.methodId, retId)
                    useFirstArgAsDispatch = false
                } else {
                    useFirstArgAsDispatch = target.owner.dispatchReceiverParameter != null

                    if (isStaticFunction(target.owner)) {
                        extractStaticTypeAccessQualifier(
                            target.owner,
                            callId,
                            locId,
                            labels.methodId,
                            retId
                        )
                    }
                }
            }

            val extensionIdxOffset: Int
            if (extensionReceiverInfo != null) {
                writeFieldAccessInFunctionBody(
                    extensionReceiverInfo.type,
                    0,
                    extensionReceiverInfo.field,
                    callId,
                    labels.methodId,
                    retId
                )
                extensionIdxOffset = 1
            } else {
                extensionIdxOffset = 0
            }

            if (bigArityParameterTypes != null) {
                // In case we're extracting a big arity function reference:
                addArgumentsToInvocationInInvokeNBody(
                    bigArityParameterTypes,
                    labels,
                    retId,
                    callId,
                    locId,
                    extensionIdxOffset,
                    useFirstArgAsDispatch,
                    dispatchReceiverIdx
                )
            } else {
                val dispatchIdxOffset = if (useFirstArgAsDispatch) 1 else 0
                for ((pIdx, p) in labels.parameters.withIndex()) {
                    val childIdx =
                        if (pIdx == 0 && useFirstArgAsDispatch) {
                            dispatchReceiverIdx
                        } else {
                            pIdx + extensionIdxOffset - dispatchIdxOffset
                        }
                    writeVariableAccessInFunctionBody(
                        p.second,
                        childIdx,
                        p.first,
                        callId,
                        labels.methodId,
                        retId
                    )
                }
            }
        }

        fun extractConstructorArguments(
            callable: Label<out DbCallable>,
            idCtorRef: Label<out DbClassinstancexpr>,
            enclosingStmt: Label<out DbStmt>
        ) {
            if (dispatchReceiverInfo != null) {
                extractExpressionExpr(
                    dispatchReceiverInfo.receiver,
                    callable,
                    idCtorRef,
                    0 + dispatchReceiverInfo.indexOffset,
                    enclosingStmt
                )
            }

            if (extensionReceiverInfo != null) {
                extractExpressionExpr(
                    extensionReceiverInfo.receiver,
                    callable,
                    idCtorRef,
                    0 + extensionReceiverInfo.indexOffset,
                    enclosingStmt
                )
            }
        }
    }

    private inner class PropertyReferenceHelper(
        callableReferenceExpr: IrCallableReference<out IrSymbol>,
        locId: Label<DbLocation>,
        ids: GeneratedClassLabels
    ) : CallableReferenceHelper(callableReferenceExpr, locId, ids) {

        fun extractPropertyReferenceInvoke(
            getId: Label<DbMethod>,
            getterParameterTypes: List<IrType>,
            getterReturnType: IrType
        ) {
            // Extracting this method is not (strictly) needed for interface member implementation.
            // `[Mutable]PropertyReferenceX` already implements it, but its signature doesn't match
            // the
            // generic one, because it's a raw method implementation. Also, by adding the `invoke`
            // explicitly,
            // we have better data flow analysis support.
            val invokeLabels =
                addFunctionManual(
                    tw.getFreshIdLabel(),
                    OperatorNameConventions.INVOKE.asString(),
                    getterParameterTypes,
                    getterReturnType,
                    classId,
                    locId
                )

            // return this.get(a0, a1, ...)
            val retId = tw.getFreshIdLabel<DbReturnstmt>()
            tw.writeStmts_returnstmt(retId, invokeLabels.blockId, 0, invokeLabels.methodId)
            tw.writeHasLocation(retId, locId)

            // Call to target function:
            val callType = useType(getterReturnType)
            val callId = tw.getFreshIdLabel<DbMethodaccess>()
            tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0)
            tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
            extractExprContext(callId, locId, invokeLabels.methodId, retId)

            tw.writeCallableBinding(callId, getId)

            this.writeThisAccess(callId, invokeLabels.methodId, retId)
            for ((pIdx, p) in invokeLabels.parameters.withIndex()) {
                this.writeVariableAccessInFunctionBody(
                    p.second,
                    pIdx,
                    p.first,
                    callId,
                    invokeLabels.methodId,
                    retId
                )
            }
        }
    }

    private val propertyRefType by lazy {
        referenceExternalClass("kotlin.jvm.internal.PropertyReference")?.typeWith()
    }

    private fun extractPropertyReference(
        exprKind: String,
        propertyReferenceExpr: IrCallableReference<out IrSymbol>,
        getter: IrSimpleFunctionSymbol?,
        setter: IrSimpleFunctionSymbol?,
        backingField: IrFieldSymbol?,
        parent: StmtExprParent,
        callable: Label<out DbCallable>
    ) {
        with(exprKind, propertyReferenceExpr) {
            /*
             * Extract generated class:
             * ```
             * class C : kotlin.jvm.internal.PropertyReference, kotlin.reflect.KMutableProperty0<R> {
             *   private dispatchReceiver: TD
             *   constructor(dispatchReceiver: TD) {
             *       super()
             *       this.dispatchReceiver = dispatchReceiver
             *   }
             *
             *   override fun get(): R { return this.dispatchReceiver.FN1() }
             *
             *   override fun set(a0: R): Unit { return this.dispatchReceiver.FN2(a0) }
             *
             *   override fun invoke(): R { return this.get() }
             * }
             * ```
             *
             * Variations:
             * - KProperty vs KMutableProperty
             * - KProperty0<> vs KProperty1<,>
             * - no receiver vs dispatchReceiver vs extensionReceiver
             **/

            val kPropertyType = propertyReferenceExpr.type
            if (kPropertyType !is IrSimpleType) {
                logger.errorElement(
                    "Unexpected: property reference with non simple type. ${kPropertyType.classFqName?.asString()}",
                    propertyReferenceExpr
                )
                return
            }
            val kPropertyClass = kPropertyType.classOrNull
            if (kPropertyClass == null) {
                logger.errorElement(
                    "Cannot find class for kPropertyType. ${kPropertyType.classFqName?.asString()}",
                    propertyReferenceExpr
                )
                return
            }
            val parameterTypes: List<IrType>? =
                kPropertyType.arguments
                    .map {
                        if (it is IrType) {
                            it
                        } else {
                            logger.errorElement(
                                "Unexpected: Non-IrType (${it.javaClass}) property reference parameter.",
                                propertyReferenceExpr
                            )
                            null
                        }
                    }
                    .requireNoNullsOrNull()
            if (parameterTypes == null) {
                logger.errorElement(
                    "Unexpected: One or more non-IrType property reference parameters.",
                    propertyReferenceExpr
                )
                return
            }

            val locId = tw.getLocation(propertyReferenceExpr)

            val javaResult = TypeResult(tw.getFreshIdLabel<DbClassorinterface>(), "", "")
            val kotlinResult = TypeResult(tw.getFreshIdLabel<DbKt_notnull_type>(), "", "")
            tw.writeKt_notnull_types(kotlinResult.id, javaResult.id)
            val ids =
                GeneratedClassLabels(
                    TypeResults(javaResult, kotlinResult),
                    constructor = tw.getFreshIdLabel(),
                    constructorBlock = tw.getFreshIdLabel()
                )

            val declarationParent =
                peekDeclStackAsDeclarationParent(propertyReferenceExpr) ?: return
            // The base class could be `Any`. `PropertyReference` is used to keep symmetry with
            // function references.
            val baseClass = propertyRefType ?: pluginContext.irBuiltIns.anyType

            val classId =
                extractGeneratedClass(
                    ids,
                    listOf(baseClass, kPropertyType),
                    locId,
                    propertyReferenceExpr,
                    declarationParent
                )

            val helper = PropertyReferenceHelper(propertyReferenceExpr, locId, ids)

            helper.extractReceiverField()

            val classTypeArguments =
                (propertyReferenceExpr.dispatchReceiver?.type as? IrSimpleType)?.arguments
                    ?: if (
                        (getter?.owner?.dispatchReceiverParameter
                            ?: setter?.owner?.dispatchReceiverParameter) != null
                    ) {
                        (kPropertyType.arguments.first() as? IrSimpleType)?.arguments
                    } else {
                        null
                    }

            val expressionTypeArguments =
                (0 until propertyReferenceExpr.typeArgumentsCount).mapNotNull {
                    propertyReferenceExpr.getTypeArgument(it)
                }

            val idPropertyRef = tw.getFreshIdLabel<DbPropertyref>()

            val getterParameterTypes = parameterTypes.dropLast(1)
            val getterReturnType = parameterTypes.last()

            if (getter != null) {
                val getterCallableId =
                    useFunction<DbCallable>(getter.owner.realOverrideTarget, classTypeArguments)
                if (getterCallableId == null) {
                    logger.errorElement("Cannot get ID for getter", propertyReferenceExpr)
                } else {
                    val getLabels =
                        addFunctionManual(
                            tw.getFreshIdLabel(),
                            OperatorNameConventions.GET.asString(),
                            getterParameterTypes,
                            getterReturnType,
                            classId,
                            locId
                        )

                    helper.extractCallToReflectionTarget(
                        getLabels,
                        getter,
                        getterReturnType,
                        expressionTypeArguments,
                        classTypeArguments
                    )

                    tw.writePropertyRefGetBinding(idPropertyRef, getterCallableId)

                    helper.extractPropertyReferenceInvoke(
                        getLabels.methodId,
                        getterParameterTypes,
                        getterReturnType
                    )
                }
            } else {
                // Property without a getter.
                if (backingField == null) {
                    logger.errorElement(
                        "Expected to find getter or backing field for property reference.",
                        propertyReferenceExpr
                    )
                    return
                }

                val getLabels =
                    addFunctionManual(
                        tw.getFreshIdLabel(),
                        OperatorNameConventions.GET.asString(),
                        getterParameterTypes,
                        getterReturnType,
                        classId,
                        locId
                    )
                val fieldId = useField(backingField.owner)

                helper.extractFieldReturnOfReflectionTarget(getLabels, backingField)

                tw.writePropertyRefFieldBinding(idPropertyRef, fieldId)

                helper.extractPropertyReferenceInvoke(
                    getLabels.methodId,
                    getterParameterTypes,
                    getterReturnType
                )
            }

            if (setter != null) {
                val setterCallableId =
                    useFunction<DbCallable>(setter.owner.realOverrideTarget, classTypeArguments)
                if (setterCallableId == null) {
                    logger.errorElement("Cannot get ID for setter", propertyReferenceExpr)
                } else {
                    val setLabels =
                        addFunctionManual(
                            tw.getFreshIdLabel(),
                            OperatorNameConventions.SET.asString(),
                            parameterTypes,
                            pluginContext.irBuiltIns.unitType,
                            classId,
                            locId
                        )

                    helper.extractCallToReflectionTarget(
                        setLabels,
                        setter,
                        pluginContext.irBuiltIns.unitType,
                        expressionTypeArguments,
                        classTypeArguments
                    )

                    tw.writePropertyRefSetBinding(idPropertyRef, setterCallableId)
                }
            } else {
                if (backingField != null && !backingField.owner.isFinal) {
                    val setLabels =
                        addFunctionManual(
                            tw.getFreshIdLabel(),
                            OperatorNameConventions.SET.asString(),
                            parameterTypes,
                            pluginContext.irBuiltIns.unitType,
                            classId,
                            locId
                        )
                    val fieldId = useField(backingField.owner)

                    helper.extractFieldWriteOfReflectionTarget(setLabels, backingField)

                    tw.writePropertyRefFieldBinding(idPropertyRef, fieldId)
                }
            }

            // Add constructor (property ref) call:
            val exprParent = parent.expr(propertyReferenceExpr, callable)
            tw.writeExprs_propertyref(
                idPropertyRef,
                ids.type.javaResult.id,
                exprParent.parent,
                exprParent.idx
            )
            tw.writeExprsKotlinType(idPropertyRef, ids.type.kotlinResult.id)
            extractExprContext(idPropertyRef, locId, callable, exprParent.enclosingStmt)
            tw.writeCallableBinding(idPropertyRef, ids.constructor)

            extractTypeAccessRecursive(
                kPropertyType,
                locId,
                idPropertyRef,
                -3,
                callable,
                exprParent.enclosingStmt
            )

            helper.extractConstructorArguments(callable, idPropertyRef, exprParent.enclosingStmt)

            tw.writeIsAnonymClass(classId, idPropertyRef)
        }
    }

    private val functionRefType by lazy {
        referenceExternalClass("kotlin.jvm.internal.FunctionReference")?.typeWith()
    }

    private fun extractFunctionReference(
        functionReferenceExpr: IrFunctionReference,
        parent: StmtExprParent,
        callable: Label<out DbCallable>
    ) {
        with("function reference", functionReferenceExpr) {
            val target =
                if (functionReferenceExpr.origin == IrStatementOrigin.ADAPTED_FUNCTION_REFERENCE)
                // For an adaptation (e.g. to adjust the number or type of arguments or results),
                // the symbol field points at the adapter while `.reflectionTarget` points at the
                // source-level target.
                functionReferenceExpr.symbol
                else
                // TODO: Consider whether we could always target the symbol
                functionReferenceExpr.reflectionTarget
                        ?: run {
                            logger.warnElement(
                                "Expected to find reflection target for function reference. Using underlying symbol instead.",
                                functionReferenceExpr
                            )
                            functionReferenceExpr.symbol
                        }

            /*
             * Extract generated class:
             * ```
             * class C : kotlin.jvm.internal.FunctionReference, kotlin.FunctionI<T0,T1, ... TI, R> {
             *   private dispatchReceiver: TD
             *   private extensionReceiver: TE
             *   constructor(dispatchReceiver: TD, extensionReceiver: TE) {
             *       super()
             *       this.dispatchReceiver = dispatchReceiver
             *       this.extensionReceiver = extensionReceiver
             *   }
             *   fun invoke(a0:T0, a1:T1, ... aI: TI): R { return this.dispatchReceiver.FN(a0,a1,...,aI) }                       OR
             *   fun invoke(       a1:T1, ... aI: TI): R { return this.dispatchReceiver.FN(this.dispatchReceiver,a1,...,aI) }    OR
             *   fun invoke(a0:T0, a1:T1, ... aI: TI): R { return Ctor(a0,a1,...,aI) }
             * }
             * ```
             * or in case of big arity lambdas ????
             * ```
             * class C : kotlin.jvm.internal.FunctionReference, kotlin.FunctionN<R> {
             *   private receiver: TD
             *   constructor(receiver: TD) { super(); this.receiver = receiver; }
             *   fun invoke(vararg args: Any?): R {
             *     return this.receiver.FN(args[0] as T0, args[1] as T1, ..., args[I] as TI)
             *   }
             * }
             * ```
             **/

            if (
                functionReferenceExpr.dispatchReceiver != null &&
                    functionReferenceExpr.extensionReceiver != null
            ) {
                logger.errorElement(
                    "Unexpected: dispatchReceiver and extensionReceiver are both non-null",
                    functionReferenceExpr
                )
                return
            }

            if (
                target.owner.dispatchReceiverParameter != null &&
                    target.owner.extensionReceiverParameter != null
            ) {
                logger.errorElement(
                    "Unexpected: dispatch and extension parameters are both non-null",
                    functionReferenceExpr
                )
                return
            }

            val type = functionReferenceExpr.type
            if (type !is IrSimpleType) {
                logger.errorElement(
                    "Unexpected: function reference with non simple type. ${type.classFqName?.asString()}",
                    functionReferenceExpr
                )
                return
            }

            val parameterTypes: List<IrType>? =
                type.arguments
                    .map {
                        if (it is IrType) {
                            it
                        } else {
                            logger.errorElement(
                                "Unexpected: Non-IrType (${it.javaClass}) function reference parameter.",
                                functionReferenceExpr
                            )
                            null
                        }
                    }
                    .requireNoNullsOrNull()
            if (parameterTypes == null) {
                logger.errorElement(
                    "Unexpected: One or more non-IrType function reference parameters.",
                    functionReferenceExpr
                )
                return
            }

            val dispatchReceiverIdx: Int
            val expressionTypeArguments: List<IrType>
            val classTypeArguments: List<IrTypeArgument>?

            if (target is IrConstructorSymbol) {
                // In case a constructor is referenced, the return type of the `KFunctionX<,,,>` is
                // the type if the constructed type.
                classTypeArguments = (type.arguments.last() as? IrSimpleType)?.arguments
                expressionTypeArguments = listOf(parameterTypes.last())
                dispatchReceiverIdx = -2
            } else {
                classTypeArguments =
                    (functionReferenceExpr.dispatchReceiver?.type as? IrSimpleType)?.arguments
                        ?: if (target.owner.dispatchReceiverParameter != null) {
                            (type.arguments.first() as? IrSimpleType)?.arguments
                        } else {
                            null
                        }
                expressionTypeArguments =
                    (0 until functionReferenceExpr.typeArgumentsCount).mapNotNull {
                        functionReferenceExpr.getTypeArgument(it)
                    }
                dispatchReceiverIdx = -1
            }

            val locId = tw.getLocation(functionReferenceExpr)

            val javaResult = TypeResult(tw.getFreshIdLabel<DbClassorinterface>(), "", "")
            val kotlinResult = TypeResult(tw.getFreshIdLabel<DbKt_notnull_type>(), "", "")
            tw.writeKt_notnull_types(kotlinResult.id, javaResult.id)
            val ids =
                LocallyVisibleFunctionLabels(
                    TypeResults(javaResult, kotlinResult),
                    constructor = tw.getFreshIdLabel(),
                    function = tw.getFreshIdLabel(),
                    constructorBlock = tw.getFreshIdLabel()
                )

            // Add constructor (member ref) call:
            val exprParent = parent.expr(functionReferenceExpr, callable)
            val idMemberRef = tw.getFreshIdLabel<DbMemberref>()
            tw.writeExprs_memberref(
                idMemberRef,
                ids.type.javaResult.id,
                exprParent.parent,
                exprParent.idx
            )
            tw.writeExprsKotlinType(idMemberRef, ids.type.kotlinResult.id)
            extractExprContext(idMemberRef, locId, callable, exprParent.enclosingStmt)
            tw.writeCallableBinding(idMemberRef, ids.constructor)

            val targetCallableId =
                useFunction<DbCallable>(target.owner.realOverrideTarget, classTypeArguments)
            if (targetCallableId == null) {
                logger.errorElement(
                    "Cannot get ID for function reference callable",
                    functionReferenceExpr
                )
            } else {
                tw.writeMemberRefBinding(idMemberRef, targetCallableId)
            }

            val helper = CallableReferenceHelper(functionReferenceExpr, locId, ids)

            val fnInterfaceType = getFunctionalInterfaceTypeWithTypeArgs(type.arguments)
            if (fnInterfaceType == null) {
                logger.warnElement(
                    "Cannot find functional interface type for function reference",
                    functionReferenceExpr
                )
            } else {
                val declarationParent =
                    peekDeclStackAsDeclarationParent(functionReferenceExpr) ?: return
                // `FunctionReference` base class is required, because that's implementing
                // `KFunction`.
                val baseClass = functionRefType ?: pluginContext.irBuiltIns.anyType

                val classId =
                    extractGeneratedClass(
                        ids,
                        listOf(baseClass, fnInterfaceType),
                        locId,
                        functionReferenceExpr,
                        declarationParent,
                        null,
                        { it.valueParameters.size == 1 }
                    ) {
                        // The argument to FunctionReference's constructor is the function arity.
                        extractConstantInteger(
                            type.arguments.size - 1,
                            locId,
                            it,
                            0,
                            ids.constructor,
                            it
                        )
                    }

                helper.extractReceiverField()

                val isBigArity = type.arguments.size > BuiltInFunctionArity.BIG_ARITY
                val funLabels =
                    if (isBigArity) {
                        addFunctionNInvoke(ids.function, parameterTypes.last(), classId, locId)
                    } else {
                        addFunctionInvoke(
                            ids.function,
                            parameterTypes.dropLast(1),
                            parameterTypes.last(),
                            classId,
                            locId
                        )
                    }

                helper.extractCallToReflectionTarget(
                    funLabels,
                    target,
                    parameterTypes.last(),
                    expressionTypeArguments,
                    classTypeArguments,
                    dispatchReceiverIdx,
                    if (isBigArity) parameterTypes.dropLast(1) else null
                )

                val typeAccessArguments =
                    if (isBigArity) listOf(parameterTypes.last()) else parameterTypes
                if (target is IrConstructorSymbol) {
                    val returnType = typeAccessArguments.last()

                    val typeAccessId =
                        extractTypeAccess(
                            useType(fnInterfaceType, TypeContext.OTHER),
                            locId,
                            idMemberRef,
                            -3,
                            callable,
                            exprParent.enclosingStmt
                        )
                    typeAccessArguments.dropLast(1).forEachIndexed { argIdx, arg ->
                        extractTypeAccessRecursive(
                            arg,
                            locId,
                            typeAccessId,
                            argIdx,
                            callable,
                            exprParent.enclosingStmt,
                            TypeContext.GENERIC_ARGUMENT
                        )
                    }

                    extractConstructorTypeAccess(
                        returnType,
                        useType(returnType),
                        target,
                        locId,
                        typeAccessId,
                        typeAccessArguments.count() - 1,
                        callable,
                        exprParent.enclosingStmt
                    )
                } else {
                    extractTypeAccessRecursive(
                        fnInterfaceType,
                        locId,
                        idMemberRef,
                        -3,
                        callable,
                        exprParent.enclosingStmt
                    )
                }

                helper.extractConstructorArguments(callable, idMemberRef, exprParent.enclosingStmt)

                tw.writeIsAnonymClass(classId, idMemberRef)
            }
        }
    }

    private fun getFunctionalInterfaceType(functionNTypeArguments: List<IrType>) =
        getFunctionalInterfaceTypeWithTypeArgs(
            functionNTypeArguments.map { makeTypeProjection(it, Variance.INVARIANT) }
        )

    private fun getFunctionalInterfaceTypeWithTypeArgs(
        functionNTypeArguments: List<IrTypeArgument>
    ) =
        if (functionNTypeArguments.size > BuiltInFunctionArity.BIG_ARITY)
            referenceExternalClass("kotlin.jvm.functions.FunctionN")
                ?.symbol
                ?.typeWithArguments(listOf(functionNTypeArguments.last()))
        else
            pluginContext.irBuiltIns.functionN(functionNTypeArguments.size - 1)
                .symbol
                .typeWithArguments(functionNTypeArguments)

    private data class FunctionLabels(
        val methodId: Label<DbMethod>,
        val blockId: Label<DbBlock>,
        val parameters: List<Pair<Label<DbParam>, TypeResults>>
    )

    /**
     * Adds a function `invoke(a: Any[])` with the specified return type to the class identified by
     * `parentId`.
     */
    private fun addFunctionNInvoke(
        methodId: Label<DbMethod>,
        returnType: IrType,
        parentId: Label<out DbReftype>,
        locId: Label<DbLocation>
    ): FunctionLabels {
        return addFunctionInvoke(
            methodId,
            listOf(pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)),
            returnType,
            parentId,
            locId
        )
    }

    /**
     * Adds a function named `invoke` with the specified parameter types and return type to the
     * class identified by `parentId`.
     */
    private fun addFunctionInvoke(
        methodId: Label<DbMethod>,
        parameterTypes: List<IrType>,
        returnType: IrType,
        parentId: Label<out DbReftype>,
        locId: Label<DbLocation>
    ): FunctionLabels {
        return addFunctionManual(
            methodId,
            OperatorNameConventions.INVOKE.asString(),
            parameterTypes,
            returnType,
            parentId,
            locId
        )
    }

    /**
     * Extracts a function with the given name, parameter types, return type, containing type, and
     * location.
     */
    private fun addFunctionManual(
        methodId: Label<DbMethod>,
        name: String,
        parameterTypes: List<IrType>,
        returnType: IrType,
        parentId: Label<out DbReftype>,
        locId: Label<DbLocation>
    ): FunctionLabels {

        val parameters =
            parameterTypes.mapIndexed { idx, p ->
                val paramId = tw.getFreshIdLabel<DbParam>()
                val paramType =
                    extractValueParameter(
                        paramId,
                        p,
                        "a$idx",
                        locId,
                        methodId,
                        idx,
                        paramId,
                        syntheticParameterNames = false,
                        isVararg = false,
                        isNoinline = false,
                        isCrossinline = false
                    )

                Pair(paramId, paramType)
            }

        val paramsSignature =
            parameters.joinToString(separator = ",", prefix = "(", postfix = ")") {
                signatureOrWarn(it.second.javaResult, declarationStack.tryPeek()?.first)
            }

        val rt = useType(returnType, TypeContext.RETURN)
        tw.writeMethods(
            methodId,
            name,
            "$name$paramsSignature",
            rt.javaResult.id,
            parentId,
            methodId
        )
        tw.writeMethodsKotlinType(methodId, rt.kotlinResult.id)
        tw.writeHasLocation(methodId, locId)

        addModifiers(methodId, "public")
        addModifiers(methodId, "override")

        return FunctionLabels(methodId, extractBlockBody(methodId, locId), parameters)
    }

    /*
     * This function generates an implementation for `fun kotlin.FunctionN<R>.invoke(vararg args: Any?): R`
     *
     * The following body is added:
     * ```
     * fun invoke(vararg a0: Any?): R {
     *   return invoke(a0[0] as T0, a0[1] as T1, ..., a0[I] as TI)
     * }
     * ```
     * */
    private fun implementFunctionNInvoke(
        lambda: IrFunction,
        ids: LocallyVisibleFunctionLabels,
        locId: Label<DbLocation>,
        parameters: List<IrValueParameter>
    ) {
        val funLabels =
            addFunctionNInvoke(
                tw.getFreshIdLabel(),
                lambda.returnType,
                ids.type.javaResult.id.cast<DbReftype>(),
                locId
            )

        // Return
        val retId = tw.getFreshIdLabel<DbReturnstmt>()
        tw.writeStmts_returnstmt(retId, funLabels.blockId, 0, funLabels.methodId)
        tw.writeHasLocation(retId, locId)

        // Call to original `invoke`:
        val callId = tw.getFreshIdLabel<DbMethodaccess>()
        val callType = useType(lambda.returnType)
        tw.writeExprs_methodaccess(callId, callType.javaResult.id, retId, 0)
        tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
        extractExprContext(callId, locId, funLabels.methodId, retId)
        val calledMethodId = useFunction<DbMethod>(lambda)
        if (calledMethodId == null) {
            logger.errorElement("Cannot get ID for called lambda", lambda)
        } else {
            tw.writeCallableBinding(callId, calledMethodId)
        }

        // this access
        extractThisAccess(ids.type, funLabels.methodId, callId, -1, retId, locId)

        addArgumentsToInvocationInInvokeNBody(
            parameters.map { it.type },
            funLabels,
            retId,
            callId,
            locId
        )
    }

    /**
     * Adds the arguments to the method call inside `invoke(a0: Any[])`. Each argument is an array
     * access with a cast:
     * ```
     * fun invoke(a0: Any[]) : T {
     *   return fn(a0[0] as T0, a0[1] as T1, ...)
     * }
     * ```
     */
    private fun addArgumentsToInvocationInInvokeNBody(
        parameterTypes: List<IrType>, // list of parameter types
        funLabels: FunctionLabels, // already generated labels for the function definition
        enclosingStmtId: Label<out DbStmt>, // label for the enclosing statement (return)
        exprParentId: Label<out DbExprparent>, // label for the expression parent (call)
        locId: Label<DbLocation>, // label for the location of all generated items
        firstArgumentOffset: Int =
            0, // 0 or 1, the index used for the first argument. 1 in case an extension parameter is
               // already accessed at index 0
        useFirstArgAsDispatch: Boolean =
            false, // true if the first argument should be used as the dispatch receiver
        dispatchReceiverIdx: Int =
            -1 // index of the dispatch receiver. -1 in case of functions, -2 in case of
               // constructors
    ) {
        val argsParamType =
            pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)
        val argsType = useType(argsParamType)
        val anyNType = useType(pluginContext.irBuiltIns.anyNType)

        val dispatchIdxOffset = if (useFirstArgAsDispatch) 1 else 0

        for ((pIdx, pType) in parameterTypes.withIndex()) {
            // `a0[i] as Ti` is generated below for each parameter

            val childIdx =
                if (pIdx == 0 && useFirstArgAsDispatch) {
                    dispatchReceiverIdx
                } else {
                    pIdx + firstArgumentOffset - dispatchIdxOffset
                }

            // cast: `(Ti)a0[i]`
            val castId = tw.getFreshIdLabel<DbCastexpr>()
            val type = useType(pType)
            tw.writeExprs_castexpr(castId, type.javaResult.id, exprParentId, childIdx)
            tw.writeExprsKotlinType(castId, type.kotlinResult.id)
            extractExprContext(castId, locId, funLabels.methodId, enclosingStmtId)

            // type access `Ti`
            extractTypeAccessRecursive(pType, locId, castId, 0, funLabels.methodId, enclosingStmtId)

            // element access: `a0[i]`
            val arrayAccessId = tw.getFreshIdLabel<DbArrayaccess>()
            tw.writeExprs_arrayaccess(arrayAccessId, anyNType.javaResult.id, castId, 1)
            tw.writeExprsKotlinType(arrayAccessId, anyNType.kotlinResult.id)
            extractExprContext(arrayAccessId, locId, funLabels.methodId, enclosingStmtId)

            // parameter access: `a0`
            val argsAccessId = tw.getFreshIdLabel<DbVaraccess>()
            tw.writeExprs_varaccess(argsAccessId, argsType.javaResult.id, arrayAccessId, 0)
            tw.writeExprsKotlinType(argsAccessId, argsType.kotlinResult.id)
            extractExprContext(argsAccessId, locId, funLabels.methodId, enclosingStmtId)
            tw.writeVariableBinding(argsAccessId, funLabels.parameters.first().first)

            // index access: `i`
            extractConstantInteger(
                pIdx,
                locId,
                arrayAccessId,
                1,
                funLabels.methodId,
                enclosingStmtId
            )
        }
    }

    private fun extractVarargElement(
        e: IrVarargElement,
        callable: Label<out DbCallable>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingStmt: Label<out DbStmt>
    ) {
        with("vararg element", e) {
            val argExpr =
                when (e) {
                    is IrExpression -> e
                    is IrSpreadElement -> e.expression
                    else -> {
                        logger.errorElement("Unrecognised IrVarargElement: " + e.javaClass, e)
                        null
                    }
                }
            argExpr?.let { extractExpressionExpr(it, callable, parent, idx, enclosingStmt) }
        }
    }

    /**
     * Extracts a type access expression and its generic arguments for a constructor call. It only
     * extracts type arguments relating to the constructed type, not the constructor itself, which
     * makes a difference in case of nested generics.
     */
    private fun extractConstructorTypeAccess(
        irType: IrType,
        type: TypeResults,
        target: IrFunctionSymbol,
        locId: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingCallable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ) {
        val typeAccessId =
            extractTypeAccess(type, locId, parent, idx, enclosingCallable, enclosingStmt)
        if (irType is IrSimpleType) {
            extractTypeArguments(
                irType.arguments
                    .take(target.owner.parentAsClass.typeParameters.size)
                    .filterIsInstance<IrType>(),
                locId,
                typeAccessId,
                enclosingCallable,
                enclosingStmt
            )
        }
    }

    /**
     * Extracts a single wildcard type access expression with no enclosing callable and statement.
     */
    private fun extractWildcardTypeAccess(
        type: TypeResultsWithoutSignatures,
        location: Label<out DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int
    ): Label<out DbExpr> {
        val id = tw.getFreshIdLabel<DbWildcardtypeaccess>()
        tw.writeExprs_wildcardtypeaccess(id, type.javaResult.id, parent, idx)
        tw.writeExprsKotlinType(id, type.kotlinResult.id)
        tw.writeHasLocation(id, location)
        return id
    }

    /** Extracts a single type access expression with no enclosing callable and statement. */
    private fun extractTypeAccess(
        type: TypeResults,
        location: Label<out DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        overrideId: Label<out DbExpr>? = null
    ): Label<out DbExpr> {
        // TODO: elementForLocation allows us to give some sort of
        //   location, but a proper location for the type access will
        //   require upstream changes
        val id = exprIdOrFresh<DbUnannotatedtypeaccess>(overrideId)
        tw.writeExprs_unannotatedtypeaccess(id, type.javaResult.id, parent, idx)
        tw.writeExprsKotlinType(id, type.kotlinResult.id)
        tw.writeHasLocation(id, location)
        return id
    }

    /** Extracts a single type access expression with enclosing callable and statement. */
    private fun extractTypeAccess(
        type: TypeResults,
        location: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        overrideId: Label<out DbExpr>? = null
    ): Label<out DbExpr> {
        val id = extractTypeAccess(type, location, parent, idx, overrideId = overrideId)
        if (enclosingCallable != null) {
            tw.writeCallableEnclosingExpr(id, enclosingCallable)
        }
        if (enclosingStmt != null) {
            tw.writeStatementEnclosingExpr(id, enclosingStmt)
        }
        return id
    }

    /**
     * Extracts a type argument type access, introducing a wildcard type access if appropriate, or
     * directly calling `extractTypeAccessRecursive` if the argument is invariant. No enclosing
     * callable and statement is extracted, this is useful for type access extraction in field
     * declarations.
     */
    private fun extractWildcardTypeAccessRecursive(
        t: IrTypeArgument,
        location: Label<out DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int
    ) {
        val typeLabels by lazy {
            TypeResultsWithoutSignatures(
                getTypeArgumentLabel(t),
                TypeResultWithoutSignature(fakeKotlinType(), Unit, "TODO")
            )
        }
        when (t) {
            is IrStarProjection -> extractWildcardTypeAccess(typeLabels, location, parent, idx)
            is IrTypeProjection ->
                when (t.variance) {
                    Variance.INVARIANT ->
                        extractTypeAccessRecursive(
                            t.type,
                            location,
                            parent,
                            idx,
                            TypeContext.GENERIC_ARGUMENT
                        )
                    else -> {
                        val wildcardLabel =
                            extractWildcardTypeAccess(typeLabels, location, parent, idx)
                        // Mimic a Java extractor oddity, that it uses the child index to indicate
                        // what kind of wildcard this is
                        val boundChildIdx = if (t.variance == Variance.OUT_VARIANCE) 0 else 1
                        extractTypeAccessRecursive(
                            t.type,
                            location,
                            wildcardLabel,
                            boundChildIdx,
                            TypeContext.GENERIC_ARGUMENT
                        )
                    }
                }
        }
    }

    /**
     * Extracts a type access expression and its child type access expressions in case of a generic
     * type. Nested generics are also handled. No enclosing callable and statement is extracted,
     * this is useful for type access extraction in field declarations.
     */
    private fun extractTypeAccessRecursive(
        t: IrType,
        location: Label<out DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        typeContext: TypeContext = TypeContext.OTHER
    ): Label<out DbExpr> {
        val typeAccessId = extractTypeAccess(useType(t, typeContext), location, parent, idx)
        if (t is IrSimpleType) {
            // From 1.9, the list might change when we call erase,
            // so we make a copy that it is safe to iterate over.
            val argumentsCopy = t.arguments.toList()
            argumentsCopy.forEachIndexed { argIdx, arg ->
                extractWildcardTypeAccessRecursive(arg, location, typeAccessId, argIdx)
            }
        }
        return typeAccessId
    }

    /**
     * Extracts a type access expression and its child type access expressions in case of a generic
     * type. Nested generics are also handled.
     */
    private fun extractTypeAccessRecursive(
        t: IrType,
        location: Label<DbLocation>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        typeContext: TypeContext = TypeContext.OTHER,
        overrideId: Label<out DbExpr>? = null
    ): Label<out DbExpr> {
        // TODO: `useType` substitutes types to their java equivalent, and sometimes that also means
        // changing the number of type arguments. The below logic doesn't take this into account.
        // For example `KFunction2<Int,Double,String>` becomes `KFunction<String>` with three child
        // type access expressions: `Int`, `Double`, `String`.
        val typeAccessId =
            extractTypeAccess(
                useType(t, typeContext),
                location,
                parent,
                idx,
                enclosingCallable,
                enclosingStmt,
                overrideId = overrideId
            )
        if (t is IrSimpleType) {
            if (t.arguments.isNotEmpty() && overrideId != null) {
                logger.error(
                    "Unexpected parameterized type with an overridden expression ID; children will be assigned fresh IDs"
                )
            }
            extractTypeArguments(
                t.arguments.filterIsInstance<IrType>(),
                location,
                typeAccessId,
                enclosingCallable,
                enclosingStmt
            )
        }
        return typeAccessId
    }

    /**
     * Extracts a list of types as type access expressions. Nested generics are also handled. Used
     * for extracting nested type access expressions, and type arguments of constructor or function
     * calls.
     */
    private fun extractTypeArguments(
        typeArgs: List<IrType>,
        location: Label<DbLocation>,
        parentExpr: Label<out DbExprparent>,
        enclosingCallable: Label<out DbCallable>?,
        enclosingStmt: Label<out DbStmt>?,
        startIndex: Int = 0,
        reverse: Boolean = false
    ) {
        typeArgs.forEachIndexed { argIdx, arg ->
            val mul = if (reverse) -1 else 1
            extractTypeAccessRecursive(
                arg,
                location,
                parentExpr,
                argIdx * mul + startIndex,
                enclosingCallable,
                enclosingStmt,
                TypeContext.GENERIC_ARGUMENT
            )
        }
    }

    /**
     * Extracts type arguments of a member access expression as type access expressions. Nested
     * generics are also handled. Used for extracting nested type access expressions, and type
     * arguments of constructor or function calls.
     */
    private fun <T : IrSymbol> extractTypeArguments(
        c: IrMemberAccessExpression<T>,
        parentExpr: Label<out DbExprparent>,
        enclosingCallable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>,
        startIndex: Int = 0,
        reverse: Boolean = false
    ) {
        val typeArguments =
            (0 until c.typeArgumentsCount).map { c.getTypeArgument(it) }.requireNoNullsOrNull()
        if (typeArguments == null) {
            logger.errorElement("Found a null type argument for a member access expression", c)
        } else {
            extractTypeArguments(
                typeArguments,
                tw.getLocation(c),
                parentExpr,
                enclosingCallable,
                enclosingStmt,
                startIndex,
                reverse
            )
        }
    }

    private fun extractArrayCreationWithInitializer(
        parent: Label<out DbExprparent>,
        arraySize: Int,
        locId: Label<DbLocation>,
        enclosingCallable: Label<out DbCallable>,
        enclosingStmt: Label<out DbStmt>
    ): Label<DbArrayinit> {

        val arrayCreationId = tw.getFreshIdLabel<DbArraycreationexpr>()
        val arrayType =
            pluginContext.irBuiltIns.arrayClass.typeWith(pluginContext.irBuiltIns.anyNType)
        val at = useType(arrayType)
        tw.writeExprs_arraycreationexpr(arrayCreationId, at.javaResult.id, parent, 0)
        tw.writeExprsKotlinType(arrayCreationId, at.kotlinResult.id)
        extractExprContext(arrayCreationId, locId, enclosingCallable, enclosingStmt)

        extractTypeAccessRecursive(
            pluginContext.irBuiltIns.anyNType,
            locId,
            arrayCreationId,
            -1,
            enclosingCallable,
            enclosingStmt
        )

        val initId = tw.getFreshIdLabel<DbArrayinit>()
        tw.writeExprs_arrayinit(initId, at.javaResult.id, arrayCreationId, -2)
        tw.writeExprsKotlinType(initId, at.kotlinResult.id)
        extractExprContext(initId, locId, enclosingCallable, enclosingStmt)

        extractConstantInteger(
            arraySize,
            locId,
            arrayCreationId,
            0,
            enclosingCallable,
            enclosingStmt
        )

        return initId
    }

    private fun extractTypeOperatorCall(
        e: IrTypeOperatorCall,
        callable: Label<out DbCallable>,
        parent: Label<out DbExprparent>,
        idx: Int,
        enclosingStmt: Label<out DbStmt>
    ) {
        with("type operator call", e) {
            when (e.operator) {
                IrTypeOperator.CAST -> {
                    val id = tw.getFreshIdLabel<DbCastexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_castexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
                }
                IrTypeOperator.IMPLICIT_CAST -> {
                    val id = tw.getFreshIdLabel<DbImplicitcastexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_implicitcastexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
                }
                IrTypeOperator.IMPLICIT_NOTNULL -> {
                    val id = tw.getFreshIdLabel<DbImplicitnotnullexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_implicitnotnullexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
                }
                IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> {
                    val id = tw.getFreshIdLabel<DbImplicitcoerciontounitexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_implicitcoerciontounitexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
                }
                IrTypeOperator.SAFE_CAST -> {
                    val id = tw.getFreshIdLabel<DbSafecastexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_safecastexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 1, enclosingStmt)
                }
                IrTypeOperator.INSTANCEOF -> {
                    val id = tw.getFreshIdLabel<DbInstanceofexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_instanceofexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 0, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 1, callable, enclosingStmt)
                }
                IrTypeOperator.NOT_INSTANCEOF -> {
                    val id = tw.getFreshIdLabel<DbNotinstanceofexpr>()
                    val locId = tw.getLocation(e)
                    val type = useType(e.type)
                    tw.writeExprs_notinstanceofexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractExpressionExpr(e.argument, callable, id, 0, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 1, callable, enclosingStmt)
                }
                IrTypeOperator.SAM_CONVERSION -> {

                    /*
                      The following Kotlin code

                      ```
                      fun interface IntPredicate {
                          fun accept(i: Int): Boolean
                      }

                      val x = IntPredicate { it % 2 == 0 }
                      ```

                      is extracted as

                      ```
                      interface IntPredicate {
                          Boolean accept(Integer i);
                      }
                      class <Anon> extends Object implements IntPredicate {
                          Function1<Integer, Boolean> <fn>;
                          public <Anon>(Function1<Integer, Boolean> <fn>) { this.<fn> = <fn>; }
                          public override Boolean accept(Integer i) { return <fn>.invoke(i); }
                      }

                      IntPredicate x = (IntPredicate)new <Anon>(...);
                      ```
                    */

                    val st = e.argument.type as? IrSimpleType
                    if (st == null) {
                        logger.errorElement("Expected to find a simple type in SAM conversion.", e)
                        return
                    }

                    fun IrSimpleType.isKProperty() =
                        classFqName?.asString()?.startsWith("kotlin.reflect.KProperty") == true

                    if (
                        !st.isFunctionOrKFunction() &&
                            !st.isSuspendFunctionOrKFunction() &&
                            !st.isKProperty()
                    ) {
                        logger.errorElement(
                            "Expected to find expression with function type in SAM conversion.",
                            e
                        )
                        return
                    }

                    // Either Function1, ... Function22 or FunctionN type, but not Function23 or
                    // above.
                    val functionType = getFunctionalInterfaceTypeWithTypeArgs(st.arguments)
                    if (functionType == null) {
                        logger.errorElement("Cannot find functional interface.", e)
                        return
                    }

                    val invokeMethod =
                        functionType.classOrNull?.owner?.declarations?.findSubType<IrFunction> {
                            it.name.asString() == OperatorNameConventions.INVOKE.asString()
                        }
                    if (invokeMethod == null) {
                        logger.errorElement(
                            "Couldn't find `invoke` method on functional interface.",
                            e
                        )
                        return
                    }

                    val typeOwner = e.typeOperand.classifierOrFail.owner
                    if (typeOwner !is IrClass) {
                        logger.errorElement(
                            "Expected to find SAM conversion to IrClass. Found '${typeOwner.javaClass}' instead. Can't implement SAM interface.",
                            e
                        )
                        return
                    }
                    val samMember =
                        typeOwner.declarations.findSubType<IrFunction> {
                            it is IrOverridableMember && it.modality == Modality.ABSTRACT
                        }
                    if (samMember == null) {
                        logger.errorElement(
                            "Couldn't find SAM member in type '${typeOwner.kotlinFqName.asString()}'. Can't implement SAM interface.",
                            e
                        )
                        return
                    }

                    val javaResult = TypeResult(tw.getFreshIdLabel<DbClassorinterface>(), "", "")
                    val kotlinResult = TypeResult(tw.getFreshIdLabel<DbKt_notnull_type>(), "", "")
                    tw.writeKt_notnull_types(kotlinResult.id, javaResult.id)
                    val ids =
                        LocallyVisibleFunctionLabels(
                            TypeResults(javaResult, kotlinResult),
                            constructor = tw.getFreshIdLabel(),
                            constructorBlock = tw.getFreshIdLabel(),
                            function = tw.getFreshIdLabel()
                        )

                    val locId = tw.getLocation(e)
                    val helper = GeneratedClassHelper(locId, ids)

                    val declarationParent = peekDeclStackAsDeclarationParent(e) ?: return
                    val classId =
                        extractGeneratedClass(
                            ids,
                            listOf(pluginContext.irBuiltIns.anyType, e.typeOperand),
                            locId,
                            e,
                            declarationParent
                        )

                    // add field
                    val fieldId = tw.getFreshIdLabel<DbField>()
                    extractField(
                        fieldId,
                        "<fn>",
                        functionType,
                        classId,
                        locId,
                        DescriptorVisibilities.PRIVATE,
                        e,
                        isExternalDeclaration = false,
                        isFinal = true,
                        isStatic = false
                    )

                    // adjust constructor
                    helper.extractParameterToFieldAssignmentInConstructor(
                        "<fn>",
                        functionType,
                        fieldId,
                        0,
                        1
                    )

                    // add implementation function
                    val classTypeArgs = (e.type as? IrSimpleType)?.arguments
                    val typeSub =
                        classTypeArgs?.let { makeGenericSubstitutionFunction(typeOwner, it) }

                    fun trySub(t: IrType, context: TypeContext) =
                        if (typeSub == null) t else typeSub(t, context, pluginContext)

                    // Force extraction of this function even if this is a fake override --
                    // This happens in the case where a functional interface inherits its only
                    // abstract member,
                    // which usually we wouldn't extract, but in this case we're effectively using
                    // it as a template
                    // for the real function we're extracting that will implement this interface,
                    // and it serves fine
                    // for that purpose. By contrast if we looked through the fake to the underlying
                    // abstract method
                    // we would need to compose generic type substitutions -- for example, if we're
                    // implementing
                    // T UnaryOperator<T>.apply(T t) here, we would need to compose substitutions so
                    // we can implement
                    // the real underlying R Function<T, R>.apply(T t).
                    forceExtractFunction(
                        samMember,
                        classId,
                        extractBody = false,
                        extractMethodAndParameterTypeAccesses = true,
                        extractAnnotations = false,
                        typeSub,
                        classTypeArgs,
                        overriddenAttributes =
                            OverriddenFunctionAttributes(
                                id = ids.function,
                                sourceLoc = tw.getLocation(e),
                                modality = Modality.FINAL
                            )
                    )

                    addModifiers(ids.function, "override")
                    if (st.isSuspendFunctionOrKFunction()) {
                        addModifiers(ids.function, "suspend")
                    }

                    // body
                    val blockId = extractBlockBody(ids.function, locId)

                    // return stmt
                    val returnId = tw.getFreshIdLabel<DbReturnstmt>()
                    tw.writeStmts_returnstmt(returnId, blockId, 0, ids.function)
                    tw.writeHasLocation(returnId, locId)

                    // <fn>.invoke(vp0, cp1, vp2, vp3, ...) or
                    // <fn>.invoke(new Object[x]{vp0, vp1, vp2, ...})

                    // Call to original `invoke`:
                    val callId = tw.getFreshIdLabel<DbMethodaccess>()
                    val callType = useType(trySub(samMember.returnType, TypeContext.RETURN))
                    tw.writeExprs_methodaccess(callId, callType.javaResult.id, returnId, 0)
                    tw.writeExprsKotlinType(callId, callType.kotlinResult.id)
                    extractExprContext(callId, locId, ids.function, returnId)
                    val calledMethodId = useFunction<DbMethod>(invokeMethod, functionType.arguments)
                    if (calledMethodId == null) {
                        logger.errorElement("Cannot get ID for called method", invokeMethod)
                    } else {
                        tw.writeCallableBinding(callId, calledMethodId)
                    }

                    // <fn> access
                    val lhsId = tw.getFreshIdLabel<DbVaraccess>()
                    val lhsType = useType(functionType)
                    tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, callId, -1)
                    tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id)
                    extractExprContext(lhsId, locId, ids.function, returnId)
                    tw.writeVariableBinding(lhsId, fieldId)

                    val parameters = mutableListOf<IrValueParameter>()
                    val extParam = samMember.extensionReceiverParameter
                    if (extParam != null) {
                        parameters.add(extParam)
                    }
                    parameters.addAll(samMember.valueParameters)

                    fun extractArgument(
                        p: IrValueParameter,
                        idx: Int,
                        parent: Label<out DbExprparent>
                    ) {
                        val argsAccessId = tw.getFreshIdLabel<DbVaraccess>()
                        val paramType = useType(trySub(p.type, TypeContext.OTHER))
                        tw.writeExprs_varaccess(argsAccessId, paramType.javaResult.id, parent, idx)
                        tw.writeExprsKotlinType(argsAccessId, paramType.kotlinResult.id)
                        extractExprContext(argsAccessId, locId, ids.function, returnId)
                        tw.writeVariableBinding(argsAccessId, useValueParameter(p, ids.function))
                    }

                    val isBigArity = st.arguments.size > BuiltInFunctionArity.BIG_ARITY
                    val argParent =
                        if (isBigArity) {
                            // <fn>.invoke(new Object[x]{vp0, vp1, vp2, ...})
                            extractArrayCreationWithInitializer(
                                callId,
                                parameters.size,
                                locId,
                                ids.function,
                                returnId
                            )
                        } else {
                            // <fn>.invoke(vp0, cp1, vp2, vp3, ...) or
                            callId
                        }

                    for ((parameterIdx, vp) in parameters.withIndex()) {
                        extractArgument(vp, parameterIdx, argParent)
                    }

                    val id = tw.getFreshIdLabel<DbCastexpr>()
                    val type = useType(e.typeOperand)
                    tw.writeExprs_castexpr(id, type.javaResult.id, parent, idx)
                    tw.writeExprsKotlinType(id, type.kotlinResult.id)
                    extractExprContext(id, locId, callable, enclosingStmt)
                    extractTypeAccessRecursive(e.typeOperand, locId, id, 0, callable, enclosingStmt)

                    val idNewexpr =
                        extractNewExpr(
                            ids.constructor,
                            ids.type,
                            locId,
                            id,
                            1,
                            callable,
                            enclosingStmt
                        )

                    tw.writeIsAnonymClass(
                        ids.type.javaResult.id.cast<DbClassorinterface>(),
                        idNewexpr
                    )

                    extractTypeAccessRecursive(
                        e.typeOperand,
                        locId,
                        idNewexpr,
                        -3,
                        callable,
                        enclosingStmt
                    )

                    extractExpressionExpr(e.argument, callable, idNewexpr, 0, enclosingStmt)
                }
                else -> {
                    logger.errorElement(
                        "Unrecognised IrTypeOperatorCall for ${e.operator}: " + e.render(),
                        e
                    )
                }
            }
        }
    }

    private fun extractBreakContinue(e: IrBreakContinue, id: Label<out DbNamedexprorstmt>) {
        with("break/continue", e) {
            val locId = tw.getLocation(e)
            tw.writeHasLocation(id, locId)
            val label = e.label
            if (label != null) {
                tw.writeNamestrings(label, "", id)
            }
        }
    }

    private val IrType.isAnonymous: Boolean
        get() = ((this as? IrSimpleType)?.classifier?.owner as? IrClass)?.isAnonymousObject ?: false

    private fun addVisibilityModifierToLocalOrAnonymousClass(id: Label<out DbModifiable>) {
        addModifiers(id, "private")
    }

    /** Extracts the class around a local function, a lambda, or a function reference. */
    private fun extractGeneratedClass(
        ids: GeneratedClassLabels,
        superTypes: List<IrType>,
        locId: Label<DbLocation>,
        elementToReportOn: IrElement,
        declarationParent: IrDeclarationParent,
        compilerGeneratedKindOverride: CompilerGeneratedKinds? = null,
        superConstructorSelector: (IrFunction) -> Boolean = { it.valueParameters.isEmpty() },
        extractSuperconstructorArgs: (Label<DbSuperconstructorinvocationstmt>) -> Unit = {},
    ): Label<out DbClassorinterface> {
        // Write class
        val id = ids.type.javaResult.id.cast<DbClassorinterface>()
        val pkgId = extractPackage("")
        tw.writeClasses_or_interfaces(id, "", pkgId, id)
        tw.writeCompiler_generated(
            id,
            (compilerGeneratedKindOverride ?: CompilerGeneratedKinds.CALLABLE_CLASS).kind
        )
        tw.writeHasLocation(id, locId)

        // Extract constructor
        val unitType = useType(pluginContext.irBuiltIns.unitType, TypeContext.RETURN)
        tw.writeConstrs(ids.constructor, "", "", unitType.javaResult.id, id, ids.constructor)
        tw.writeConstrsKotlinType(ids.constructor, unitType.kotlinResult.id)
        tw.writeHasLocation(ids.constructor, locId)
        addModifiers(ids.constructor, "public")

        // Constructor body
        val constructorBlockId = ids.constructorBlock
        tw.writeStmts_block(constructorBlockId, ids.constructor, 0, ids.constructor)
        tw.writeHasLocation(constructorBlockId, locId)

        // Super call
        val baseClass = superTypes.first().classOrNull
        if (baseClass == null) {
            logger.warnElement("Cannot find base class", elementToReportOn)
        } else {
            val baseConstructor =
                baseClass.owner.declarations.findSubType<IrFunction> {
                    it.symbol is IrConstructorSymbol && superConstructorSelector(it)
                }
            if (baseConstructor == null) {
                logger.warnElement("Cannot find base constructor", elementToReportOn)
            } else {
                val baseConstructorId = useFunction<DbConstructor>(baseConstructor)
                if (baseConstructorId == null) {
                    logger.errorElement("Cannot find base constructor ID", elementToReportOn)
                } else {
                    val superCallId = tw.getFreshIdLabel<DbSuperconstructorinvocationstmt>()
                    tw.writeStmts_superconstructorinvocationstmt(
                        superCallId,
                        constructorBlockId,
                        0,
                        ids.constructor
                    )

                    tw.writeHasLocation(superCallId, locId)
                    tw.writeCallableBinding(superCallId.cast<DbCaller>(), baseConstructorId)
                    extractSuperconstructorArgs(superCallId)
                }
            }
        }

        addModifiers(id, "final")
        addVisibilityModifierToLocalOrAnonymousClass(id)
        extractClassSupertypes(
            superTypes,
            listOf(),
            id,
            isInterface = false,
            inReceiverContext = true
        )

        extractEnclosingClass(declarationParent, id, null, locId, listOf())

        return id
    }

    /**
     * Extracts the class around a local function or a lambda. The superclass must have a no-arg
     * constructor.
     */
    private fun extractGeneratedClass(
        localFunction: IrFunction,
        superTypes: List<IrType>,
        compilerGeneratedKindOverride: CompilerGeneratedKinds? = null
    ): Label<out DbClassorinterface> {
        with("generated class", localFunction) {
            val ids = getLocallyVisibleFunctionLabels(localFunction)

            val id =
                extractGeneratedClass(
                    ids,
                    superTypes,
                    tw.getLocation(localFunction),
                    localFunction,
                    localFunction.parent,
                    compilerGeneratedKindOverride = compilerGeneratedKindOverride
                )

            // Extract local function as a member
            extractFunction(
                localFunction,
                id,
                extractBody = true,
                extractMethodAndParameterTypeAccesses = true,
                extractAnnotations = false,
                null,
                listOf()
            )

            return id
        }
    }

    private inner class DeclarationStackAdjuster(
        val declaration: IrDeclaration,
        val overriddenAttributes: OverriddenFunctionAttributes? = null
    ) : Closeable {
        init {
            declarationStack.push(declaration, overriddenAttributes)
        }

        override fun close() {
            declarationStack.pop()
        }
    }

    class DeclarationStack {
        private val stack: Stack<Pair<IrDeclaration, OverriddenFunctionAttributes?>> = Stack()

        fun push(item: IrDeclaration, overriddenAttributes: OverriddenFunctionAttributes?) =
            stack.push(Pair(item, overriddenAttributes))

        fun pop() = stack.pop()

        fun isEmpty() = stack.isEmpty()

        fun peek() = stack.peek()

        fun tryPeek() = if (stack.isEmpty()) null else stack.peek()

        fun findOverriddenAttributes(f: IrFunction) = stack.lastOrNull { it.first == f }?.second
    }

    data class OverriddenFunctionAttributes(
        val id: Label<out DbCallable>? = null,
        val sourceDeclarationId: Label<out DbCallable>? = null,
        val sourceLoc: Label<DbLocation>? = null,
        val valueParameters: List<IrValueParameter>? = null,
        val typeParameters: List<IrTypeParameter>? = null,
        val isStatic: Boolean? = null,
        val visibility: DescriptorVisibility? = null,
        val modality: Modality? = null,
    )

    private fun peekDeclStackAsDeclarationParent(
        elementToReportOn: IrElement
    ): IrDeclarationParent? {
        val trapWriter = tw
        if (declarationStack.isEmpty() && trapWriter is SourceFileTrapWriter) {
            // If the current declaration is used as a parent, we might end up with an empty stack.
            // In this case, the source file is the parent.
            return trapWriter.irFile
        }

        val dp = declarationStack.peek().first as? IrDeclarationParent
        if (dp == null)
            logger.errorElement("Couldn't find current declaration parent", elementToReportOn)
        return dp
    }

    private enum class CompilerGeneratedKinds(val kind: Int) {
        DECLARING_CLASSES_OF_ADAPTER_FUNCTIONS(1),
        GENERATED_DATA_CLASS_MEMBER(2),
        DEFAULT_PROPERTY_ACCESSOR(3),
        CLASS_INITIALISATION_METHOD(4),
        ENUM_CLASS_SPECIAL_MEMBER(5),
        DELEGATED_PROPERTY_GETTER(6),
        DELEGATED_PROPERTY_SETTER(7),
        JVMSTATIC_PROXY_METHOD(8),
        JVMOVERLOADS_METHOD(9),
        DEFAULT_ARGUMENTS_METHOD(10),
        INTERFACE_FORWARDER(11),
        ENUM_CONSTRUCTOR_ARGUMENT(12),
        CALLABLE_CLASS(13),
    }
}
